From 9dce353f7e3461728a4ad3eb8d0ebd488dc4d7de Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Tue, 23 Mar 2021 14:43:28 -0700 Subject: [PATCH 01/33] Initial revision --- .../CHANGELOG.md | 4 + .../MANIFEST.in | 6 + .../README.md | 96 +++ .../azure/__init__.py | 1 + .../azure/messaging/__init__.py | 1 + .../messaging/webpubsubservice/__init__.py | 91 +++ .../messaging/webpubsubservice/_policies.py | 99 +++ .../messaging/webpubsubservice/_version.py | 6 + .../webpubsubservice/core/__init__.py | 0 .../messaging/webpubsubservice/core/rest.py | 274 ++++++++ .../azure/messaging/webpubsubservice/rest.py | 651 ++++++++++++++++++ .../examples/send_messages.py | 40 ++ .../azure-messaging-webpubsubservice/setup.py | 75 ++ .../tests/test_send_requests.py | 15 + 14 files changed, 1359 insertions(+) create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/CHANGELOG.md create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/README.md create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/__init__.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/__init__.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_version.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/__init__.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/setup.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/tests/test_send_requests.py diff --git a/sdk/signalr/azure-messaging-webpubsubservice/CHANGELOG.md b/sdk/signalr/azure-messaging-webpubsubservice/CHANGELOG.md new file mode 100644 index 000000000000..6169dd8966fe --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/CHANGELOG.md @@ -0,0 +1,4 @@ +## 1.0.0b1 + +Initial version + diff --git a/sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in b/sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in new file mode 100644 index 000000000000..2094336fbe77 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in @@ -0,0 +1,6 @@ +include *.md +include azure/__init__.py +include azure/messaging/__init__.py +include LICENSE.txt +recursive-include tests *.py +recursive-include samples *.py *.md \ No newline at end of file diff --git a/sdk/signalr/azure-messaging-webpubsubservice/README.md b/sdk/signalr/azure-messaging-webpubsubservice/README.md new file mode 100644 index 000000000000..05c896228ea3 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/README.md @@ -0,0 +1,96 @@ +# AzureWebPubSub Service client library for Python + +[Source code](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/signalr/azure-messaging-webpubsubservice) | [Package (Pypi)][package] | [API reference documentation](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/signalr/azure-messaging-webpubsubservice) | [Product documentation][webpubsubservice_docs] + +## Getting started + +### Installating the package + +```bash +python -m pip install azure-messaging-webpubsubservice +``` + +#### Prequisites + +* Python 2.7, or 3.6 or later is required to use this package. +* You need an [Azure subscription][azure_sub], and a [Azure WebPubSub service instance][webpubsubservice] to use this package. + +### Authenticating the client + +In order to interact with the Azure WebPubSub service, you'll need to create an instance of the [WebPubSubServiceClient][webpubsubservice_client_class] class. In order to authenticate against the service, you need to pass in an AzureKeyCredential instance with endpoint and api key. The endpoint and api key can be found on the azure portal. + +```python +>>> from azure.messaging.webpubsubservice import WebPubSubServiceClient +>>> from azure.core.credentials import AzureKeyCredential +>>> client = WebPubSubServiceClient(endpoint='', credential=AzureKeyCredential('somesecret')) +>>> client +'> +``` + +### Sending a request + +```python +>>> from azure.messaging.webpubsubservice import WebPubSubServiceClient +>>> from azure.core.credentials import AzureKeyCredential +>>> from azure.messaging.webpubsubservicerest import prepare_send_to_all +>>> client = WebPubSubServiceClient(endpoint='', credential=AzureKeyCredential('somesecret')) +>>> request = prepare_send_to_all('default', 'Hello, webpubsub!') +>>> request + +>>> response = client.send_request() +>>> response + +>>> response.status_code +202 +``` + +## Key concepts + +### Hubs, groups, connections and users + + + +## Troubleshooting + +### Logging + +This SDK uses Python standard logging library. +You can configure logging print out debugging information to the stdout or anywhere you want. + +```python +import logging + +logging.basicConfig(level=logging.DEBUG) +```` + +Http request and response details are printed to stdout with this logging config. + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require +you to agree to a Contributor License Agreement (CLA) declaring that you have +the right to, and actually do, grant us the rights to use your contribution. +For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether +you need to provide a CLA and decorate the PR appropriately (e.g., label, +comment). Simply follow the instructions provided by the bot. You will only +need to do this once across all repos using our CLA. + +This project has adopted the +[Microsoft Open Source Code of Conduct][code_of_conduct]. For more information, +see the Code of Conduct FAQ or contact opencode@microsoft.com with any +additional questions or comments. + + +[webpubsubservice_docs]: https://docs.microsoft.com/azure/azure-messaging-webpubsubservice/ +[azure_cli]: https://docs.microsoft.com/cli/azure +[azure_sub]: https://azure.microsoft.com/free/ +[webpubsubservice_client_class]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +[package]: https://pypi.org/project/azure-messaging-webpubsubservice/ +[webpubsubservice]: https://azure.microsoft.com/services/webpubsubservice/ +[default_cred_ref]: https://aka.ms/azsdk-python-identity-default-cred-ref +[cla]: https://cla.microsoft.com +[code_of_conduct]: https://opensource.microsoft.com/codeofconduct/ +[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/ +[coc_contact]: mailto:opencode@microsoft.com diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/__init__.py new file mode 100644 index 000000000000..8db66d3d0f0f --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/__init__.py new file mode 100644 index 000000000000..8db66d3d0f0f --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py new file mode 100644 index 000000000000..df973fd5cebb --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -0,0 +1,91 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +__all__ = ["WebPubSubServiceClient"] + +from typing import TYPE_CHECKING + + +import azure.core.pipeline as corepipeline +import azure.core.pipeline.policies as corepolicies +import azure.core.pipeline.transport as coretransport + +# Temporary location for types that eventually graduate to Azure Core +from .core import rest as corerest +from ._policies import JwtCredentialPolicy + +if TYPE_CHECKING: + import azure.core.credentials as corecredentials + from azure.core.pipeline.policies import HTTPPolicy, SansIOHTTPPolicy + from typing import Any, List, cast + + +class WebPubSubServiceClient(object): + def __init__(self, endpoint, credential, **kwargs): + # type: (str, corecredentials.AzureKeyCredential, Any) -> None + """Create a new WebPubSubServiceClient instance + + :param endpoint: Endpoint to connect to. + :type endpoint: ~str + :param credential: Credentials to use to connect to endpoint. + :type credential: ~azure.core.credentials.AzureKeyCredentials + :keyword api_version: Api version to use when communicating with the service. + :type api_version: str + :keyword user: User to connect as. Optional. + :type user: ~str + """ + self.endpoint = endpoint.rstrip("/") + transport = kwargs.pop("transport", None) or coretransport.RequestsTransport( + **kwargs + ) + policies = [ + corepolicies.HeadersPolicy(**kwargs), + corepolicies.UserAgentPolicy(**kwargs), + corepolicies.RetryPolicy(**kwargs), + corepolicies.ProxyPolicy(**kwargs), + corepolicies.CustomHookPolicy(**kwargs), + corepolicies.RedirectPolicy(**kwargs), + JwtCredentialPolicy(credential, kwargs.get("user", None)), + corepolicies.NetworkTraceLoggingPolicy(**kwargs), + ] # type: Any + self._pipeline = corepipeline.Pipeline( + transport, + policies, + ) # type: corepipeline.Pipeline + + def __repr__(self): + return " endpoint:'{}'".format(self.endpoint) + + def _format_url(self, url): + # type: (str) -> str + assert self.endpoint[-1] != "/", "My endpoint should not have a trailing slash" + return "/".join([self.endpoint, url.lstrip("/")]) + + def send_request(self, request, **kwargs): + # type: (corerest.HttpRequest, Any) -> corerest.HttpResponse + """Runs the network request through the client's chained policies. + + :param request: The network request you want to make. Required. + :type request: ~corerest.HttpRequest + :keyword bool stream: Whether the response payload will be streamed. Defaults to False + :return: The response of your network call. + :rtype: ~corerest.HttpResponse + """ + kwargs.setdefault("stream", False) + request.url = self._format_url( + request.url + ) # BUGBUG - should create new request, not mutate the existing one... + pipeline_response = self._pipeline.run( + request._internal_request, **kwargs) # pylint: disable=W0212 + return corerest.HttpResponse( + request=request, + _internal_response=pipeline_response.http_response, + ) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py new file mode 100644 index 000000000000..6020748264ec --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py @@ -0,0 +1,99 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- + +import datetime +import typing +import jwt + +from azure.core.pipeline.policies import SansIOHTTPPolicy +if typing.TYPE_CHECKING: + from azure.core.credentials import AzureKeyCredential + from azure.core.pipeline import PipelineRequest + + +class _UTC_TZ(datetime.tzinfo): + """from https://docs.python.org/2/library/datetime.html#tzinfo-objects""" + + ZERO = datetime.timedelta(0) + + def utcoffset(self, dt): + return self.__class__.ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return self.__class__.ZERO + + +_UTC = _UTC_TZ() + +class JwtCredentialPolicy(SansIOHTTPPolicy): + + NAME_CLAIM_TYPE = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" + + def __init__(self, credential, user=None): + # type: (AzureKeyCredential, typing.Optional[str]) -> None + """Create a new instance of the policy associated with the given credential. + + :param credential: The azure.core.credentials.AzureKeyCredential instance to use + :type credential: ~azure.core.credentials.AzureKeyCredential + :param user: Optional user name associated with the credential. + :type user: str + """ + self._credential = credential + self._user = user + + def on_request(self, request): + # type: (PipelineRequest) -> typing.Union[None, typing.Awaitable[None]] + """Is executed before sending the request from next policy. + + :param request: Request to be modified before sent from next policy. + :type request: ~azure.core.pipeline.PipelineRequest + """ + request.http_request.headers["Authorization"] = "Bearer " + self._encode( + request.http_request.url + ) + return super(JwtCredentialPolicy, self).on_request(request) # pylint: disable=R1725 + + def _encode(self, url): + # type: (AzureKeyCredential) -> str + data = { + "aud": url, + "exp": datetime.datetime.now(tz=_UTC) + + datetime.timedelta(seconds=60), + } + if self._user: + data[self.NAME_CLAIM_TYPE] = self._user + + encoded = jwt.encode( + payload=data, + key=self._credential.key, + algorithm="HS256", + ) + if isinstance(encoded, bytes): + encoded = encoded.decode('utf8') + return typing.cast(str, encoded) # jwt's typing is incorrect... diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_version.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_version.py new file mode 100644 index 000000000000..ac9f392f513e --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_version.py @@ -0,0 +1,6 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +VERSION = "1.0.0b1" diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py new file mode 100644 index 000000000000..c683818e7bd4 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py @@ -0,0 +1,274 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- + +__all__ = ["HttpRequest", "HttpResponse"] + +import json as jsonlib +import xml.etree.ElementTree as ET +from typing import TYPE_CHECKING + +from azure.core.pipeline.transport import ( + HttpRequest as _PipelineTransportHttpRequest, + HttpResponse as _PipelineTransportHttpResponse, +) + +if TYPE_CHECKING: + from typing import Any, Optional, Union, Mapping, Sequence, Tuple + HeaderTypes = Union[Mapping[str, str], Sequence[Tuple[str, str]]] + + +def _is_stream(content): + return isinstance(content, (str, bytes)) or any( + hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] + ) + + +class HttpRequest(object): + """Represents an HTTP request. + + :param method: HTTP method (GET, HEAD, etc.) + :type method: str or ~azure.core.protocol.HttpVerbs + :param str url: The url for your request + :keyword params: Query parameters to be mapped into your URL. Your input + should be a mapping or sequence of query name to query value(s). + :paramtype params: mapping or sequence + :keyword headers: HTTP headers you want in your request. Your input should + be a mapping or sequence of header name to header value. + :paramtype headers: mapping or sequence + :keyword dict data: Form data you want in your request body. Use for form-encoded data, i.e. + HTML forms. + :keyword any json: A JSON serializable object. We handle JSON-serialization for your + object, so use this for more complicated data structures than `data`. + :keyword files: Files you want to in your request body. Use for uploading files with + multipart encoding. Your input should be a mapping or sequence of file name to file content. + Use the `data` kwarg in addition if you want to include non-file data files as part of your request. + :paramtype files: mapping or sequence + :keyword content: Content you want in your request body. Think of it as the kwarg you should input + if your data doesn't fit into `json`, `data`, or `files`. Accepts a bytes type, or a generator + that yields bytes. + :paramtype content: str or bytes or iterable[bytes] or asynciterable[bytes] + """ + + def __init__(self, method, url, **kwargs): + # type: (str, str, Any) -> None + + data = kwargs.pop("data", None) + content = kwargs.pop("content", None) + json = kwargs.pop("json", None) + files = kwargs.pop("files", None) + + self._internal_request = _PipelineTransportHttpRequest( + method=method, + url=url, + headers=kwargs.pop("headers", None), + ) + params = kwargs.pop("params", None) + + if params: + self._internal_request.format_parameters(params) + if data is not None: + self._internal_request.set_formdata_body(data) + if content is not None: + content_type = self._internal_request.headers.get("Content-Type") + if _is_stream(content): + self._internal_request.set_streamed_data_body(content) + elif isinstance(content, ET.Element): + self._internal_request.set_xml_body(content) + elif content_type and content_type.startswith("text/"): + self._internal_request.set_text_body(content) + else: + self._internal_request.data = content + if json is not None: + self._internal_request.set_json_body(json) + if not self._internal_request.headers.get("Content-Type"): + self._internal_request.headers["Content-Type"] = "application/json" + if files is not None: + self._internal_request.set_formdata_body(files) + + if not self._internal_request.headers.get("Content-Length"): + try: + # set content length header if possible + self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) # type: ignore + except TypeError: + pass + self.method = self._internal_request.method + if kwargs: + raise TypeError( + "You have passed in kwargs '{}' that are not valid kwargs.".format( + "', '".join(list(kwargs.keys())) + ) + ) + + @property + def url(self): + # type: (...) -> str + return self._internal_request.url + + @url.setter + def url(self, val): + # type: (str) -> None + self._internal_request.url = val + + @property + def method(self): + # type: (...) -> str + return self._internal_request.method + + @method.setter + def method(self, val): + # type: (str) -> None + self._internal_request.method = val + + @property + def headers(self): + return self._internal_request.headers + + @property + def content(self): + return self._internal_request.data + + def __repr__(self): + return "".format(self.method, self.url) + + def __deepcopy__(self, memo=None): + return self._internal_request.__deepcopy__(memo) + + +class _HttpResponseBase(object): + """Base class for HttpResponse and AsyncHttpResponse. + :param int status_code: Status code of the response. + :keyword headers: Response headers + :paramtype headers: dict[str, any] + :keyword str text: The response content as a string + :keyword any json: JSON content + :keyword stream: Streamed response + :paramtype stream: bytes or iterator of bytes + :keyword callable on_close: Any callable you want to cal + when closing your HttpResponse + :keyword history: If redirection, history of all redirection + that resulted in this response. + :paramtype history: list[~azure.core.protocol.HttpResponse] + """ + + def __init__(self, **kwargs): + # type: (Any) -> None + self._internal_response = kwargs.pop( + "_internal_response" + ) # type: _PipelineTransportHttpResponse + self.request = kwargs.pop("request") + self._encoding = "" + + @property + def status_code(self): + # type: (...) -> int + """Returns the status code of the response""" + return self._internal_response.status_code or -1 + + @status_code.setter + def status_code(self, val): + # type: (int) -> None + """Set the status code of the response""" + self._internal_response.status_code = val + + @property + def headers(self): + # type: (...) -> HeaderTypes + """Returns the response headers""" + return self._internal_response.headers + + @property + def reason(self): + # type: (...) -> str + """Returns the reason phrase for the response""" + return self._internal_response.reason or "" + + @property + def content(self): + # type: (...) -> bytes + """Returns the response content in bytes""" + raise NotImplementedError() + + @property + def url(self): + # type: (...) -> str + """Returns the URL that resulted in this response""" + return self._internal_response.request.url + + @property + def encoding(self): + # type: (...) -> Optional[str] + """Returns the response encoding. By default, is specified + by the response Content-Type header. + """ + return self._encoding + + @encoding.setter + def encoding(self, value): + # type: (str) -> None + """Sets the response encoding""" + self._encoding = value + + @property + def text(self): + # type: (...) -> str + """Returns the response body as a string""" + return self._internal_response.text(self.encoding) + + def json(self): + # type: (...) -> Any + """Returns the whole body as a json object. + + :return: The JSON deserialized response body + :rtype: any + :raises json.decoder.JSONDecodeError or ValueError (in python 2.7) if object is not JSON decodable: + """ + return jsonlib.loads(self._internal_response.text(self.encoding)) + + def raise_for_status(self): + # type: () -> None + """Raises an HttpResponseError if the response has an error status code. + + If response is good, does nothing. + """ + self._internal_response.raise_for_status() + + def __repr__(self): + # type: (...) -> str + return repr(self._internal_response) + + +class HttpResponse(_HttpResponseBase): + @property + def content(self): + # type: (...) -> bytes + return self._internal_response.body() + + +class AsyncHttpResponse(_HttpResponseBase): + @property + def content(self): + # type: (...) -> bytes + return self._internal_response.body() diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py new file mode 100644 index 000000000000..8ed0fce1a348 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py @@ -0,0 +1,651 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +__all__ = [ + 'prepare_add_connection_to_group', + 'prepare_add_user_to_group', + 'prepare_check_connection_existence', + 'prepare_check_group_existence', + 'prepare_check_permission', + 'prepare_check_user_existence', + 'prepare_check_user_existence_in_group', + 'prepare_close_client_connection', + 'prepare_grant_permission', + 'prepare_healthapi_get_health_status', + 'prepare_remove_connection_from_group', + 'prepare_remove_user_from_all_groups', + 'prepare_remove_user_from_group', + 'prepare_revoke_permission', + 'prepare_send_to_all', + 'prepare_send_to_connection', + 'prepare_send_to_group', + 'prepare_send_to_user' +] + +from typing import TYPE_CHECKING + +from msrest import Serializer +from azure.messaging.webpubsubservice.core.rest import HttpRequest +from azure.core.pipeline.transport._base import _format_url_section + +if TYPE_CHECKING: + # pylint: disable=unused-import,ungrouped-imports + from typing import Any, IO, List, Optional, Union, Dict + try: + from typing import Literal + except ImportError: # < 3.8 + from typing_extensions import Literal + Permissions = Union[Literal['joinLeaveGroup'], Literal['sendToGroup']] + +_SERIALIZER = Serializer() + + +def prepare_healthapi_get_health_status( + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/health') + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="HEAD", + url=url, + params=query_parameters, + ) + + +def prepare_send_to_all( + hub, # type: str + body, + **kwargs # type: Any +): + # type: (...) -> HttpRequest + excluded = kwargs.pop('excluded', None) # type: Optional[List[str]] + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + content_type = kwargs.pop("content_type", "application/octet-stream") + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/:send') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if excluded is not None: + query_parameters['excluded'] = [ + _SERIALIZER.query("excluded", q, 'str') if q is not None else '' for q in excluded + ] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + if header_parameters['Content-Type'].split(";")[0] in ['application/octet-stream']: + body_content_kwargs['content'] = body + + elif header_parameters['Content-Type'].split(";")[0] in ['text/plain']: + body_content_kwargs['content'] = body + elif header_parameters['Content-Type'].split(";")[0] in ['application/json']: + body_content_kwargs['content'] = body + + + return HttpRequest( + method="POST", + url=url, + params=query_parameters, + headers=header_parameters, + **body_content_kwargs + ) + + +def prepare_check_connection_existence( + hub, # type: str + connection_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="HEAD", + url=url, + params=query_parameters, + ) + + +def prepare_close_client_connection( + hub, # type: str + connection_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + reason = kwargs.pop('reason', None) # type: Optional[str] + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if reason is not None: + query_parameters['reason'] = _SERIALIZER.query("reason", reason, 'str') + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="DELETE", + url=url, + params=query_parameters, + ) + + +def prepare_send_to_connection( + hub, # type: str + connection_id, # type: str + body, # type: Union[IO, str, Dict] + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + content_type = kwargs.pop("content_type", "application/octet-stream") + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}/:send') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + if header_parameters['Content-Type'].split(";")[0] in ['application/octet-stream']: + body_content_kwargs['content'] = body + + elif header_parameters['Content-Type'].split(";")[0] in ['text/plain']: + body_content_kwargs['content'] = body + elif header_parameters['Content-Type'].split(";")[0] in ['application/json']: + body_content_kwargs['content'] = body + + + return HttpRequest( + method="POST", + url=url, + params=query_parameters, + headers=header_parameters, + **body_content_kwargs + ) + + +def prepare_check_group_existence( + hub, # type: str + group, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'group': _SERIALIZER.url("group", group, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="HEAD", + url=url, + params=query_parameters, + ) + + +def prepare_send_to_group( + hub, # type: str + group, # type: str + body, # type: Union[IO, str, Dict] + **kwargs # type: Any +): + # type: (...) -> HttpRequest + excluded = kwargs.pop('excluded', None) # type: Optional[List[str]] + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + content_type = kwargs.pop("content_type", "application/octet-stream") + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/:send') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'group': _SERIALIZER.url("group", group, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if excluded is not None: + query_parameters['excluded'] = [ + _SERIALIZER.query("excluded", q, 'str') if q is not None else '' for q in excluded + ] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + if header_parameters['Content-Type'].split(";")[0] in ['application/octet-stream']: + body_content_kwargs['content'] = body + + elif header_parameters['Content-Type'].split(";")[0] in ['text/plain']: + body_content_kwargs['content'] = body + elif header_parameters['Content-Type'].split(";")[0] in ['application/json']: + body_content_kwargs['content'] = body + + + return HttpRequest( + method="POST", + url=url, + params=query_parameters, + headers=header_parameters, + **body_content_kwargs + ) + + +def prepare_add_connection_to_group( + hub, # type: str + group, # type: str + connection_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/connections/{connectionId}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'group': _SERIALIZER.url("group", group, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="PUT", + url=url, + params=query_parameters, + ) + + +def prepare_remove_connection_from_group( + hub, # type: str + group, # type: str + connection_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/connections/{connectionId}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'group': _SERIALIZER.url("group", group, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="DELETE", + url=url, + params=query_parameters, + ) + + +def prepare_check_user_existence( + hub, # type: str + user_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="HEAD", + url=url, + params=query_parameters, + ) + + +def prepare_send_to_user( + hub, # type: str + user_id, # type: str + body, # type: Union[IO, str, Dict] + + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + content_type = kwargs.pop("content_type", "application/octet-stream") + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/:send') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + if header_parameters['Content-Type'].split(";")[0] in ['application/octet-stream']: + body_content_kwargs['content'] = body + + elif header_parameters['Content-Type'].split(";")[0] in ['text/plain']: + body_content_kwargs['content'] = body + elif header_parameters['Content-Type'].split(";")[0] in ['application/json']: + body_content_kwargs['content'] = body + + + return HttpRequest( + method="POST", + url=url, + params=query_parameters, + headers=header_parameters, + **body_content_kwargs + ) + + +def prepare_check_user_existence_in_group( + hub, # type: str + group, # type: str + user_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'group': _SERIALIZER.url("group", group, 'str'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="HEAD", + url=url, + params=query_parameters, + ) + + +def prepare_add_user_to_group( + hub, # type: str + group, # type: str + user_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'group': _SERIALIZER.url("group", group, 'str'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="PUT", + url=url, + params=query_parameters, + ) + + +def prepare_remove_user_from_group( + hub, # type: str + group, # type: str + user_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'group': _SERIALIZER.url("group", group, 'str'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="DELETE", + url=url, + params=query_parameters, + ) + + +def prepare_remove_user_from_all_groups( + hub, # type: str + user_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="DELETE", + url=url, + params=query_parameters, + ) + + +def prepare_grant_permission( + hub, # type: str + permission, # type: Permissions + connection_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + target_name = kwargs.pop('target_name', None) # type: Optional[str] + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'permission': _SERIALIZER.url("permission", permission, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if target_name is not None: + query_parameters['targetName'] = _SERIALIZER.query("target_name", target_name, 'str') + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="PUT", + url=url, + params=query_parameters, + ) + + +def prepare_revoke_permission( + hub, # type: str + permission, # type: Permissions + connection_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + target_name = kwargs.pop('target_name', None) # type: Optional[str] + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'permission': _SERIALIZER.url("permission", permission, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if target_name is not None: + query_parameters['targetName'] = _SERIALIZER.query("target_name", target_name, 'str') + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="DELETE", + url=url, + params=query_parameters, + ) + + +def prepare_check_permission( + hub, # type: str + permission, # type: Permissions + connection_id, # type: str + **kwargs # type: Any +): + # type: (...) -> HttpRequest + target_name = kwargs.pop('target_name', None) # type: Optional[str] + api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + + # Construct URL + url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') + path_format_arguments = { + 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'permission': _SERIALIZER.url("permission", permission, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + } + url = _format_url_section(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + if target_name is not None: + query_parameters['targetName'] = _SERIALIZER.query("target_name", target_name, 'str') + if api_version is not None: + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') + + return HttpRequest( + method="HEAD", + url=url, + params=query_parameters, + ) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py new file mode 100644 index 000000000000..4c6456ca239b --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py @@ -0,0 +1,40 @@ +import os + +from azure.core.credentials import AzureKeyCredential +from azure.messaging.webpubsubservice import WebPubSubServiceClient +from azure.messaging.webpubsubservice.rest import * + +endpoint = os.environ['ENDPOINT'] +api_key = os.environ['API_KEY'] + +client = WebPubSubServiceClient(endpoint, credential=AzureKeyCredential(api_key)) + +# Send message to everybody on the given hub... +request = prepare_send_to_all('ahub', 'Hello, all!') +response = client.send_request(request) +try: + response.raise_for_status() + print('Successfully sent a message') +except: + print('Failed to send message: {}'.format(response)) + + +# Add a user to a group +request = prepare_add_user_to_group('ahub', group='someGroup', user_id='me') +response = client.send_request(request) +try: + response.raise_for_status() +except: + print('Failed to add user to group: {}'.format(response)) + + +# Add a connection to a group +request = prepare_add_connection_to_group(hub='ahub', group='thegroup', connection_id='`7') +response = client.send_request(request) +print(response) + +# Check if a group exists +request = prepare_check_group_existence(hub='ahub', group='missing') +response = client.send_request(request) +print(response) + diff --git a/sdk/signalr/azure-messaging-webpubsubservice/setup.py b/sdk/signalr/azure-messaging-webpubsubservice/setup.py new file mode 100644 index 000000000000..914125ece0e8 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/setup.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + + +import os +import re + +from setuptools import setup, find_packages + + +# Change the PACKAGE_NAME only to change folder and different name +PACKAGE_NAME = "azure-messaging-webpubsubservice" +PACKAGE_PPRINT_NAME = "Azure WebPubSub Service" + +# a-b-c => a/b/c +package_folder_path = PACKAGE_NAME.replace("-", "/") + + +# Version extraction inspired from 'requests' +with open(os.path.join(package_folder_path, "_version.py"), "r") as fd: + version = re.search( + r'^VERSION\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE + ).group(1) + +if not version: + raise RuntimeError("Cannot find version information") + +setup( + name=PACKAGE_NAME, + version=version, + description="Microsoft {} Client Library for Python".format(PACKAGE_PPRINT_NAME), + long_description=open("README.md", "r").read(), + long_description_content_type="text/markdown", + license="MIT License", + author="Microsoft Corporation", + author_email="azpysdkhelp@microsoft.com", + url="https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/signalr/azure-messaging-webpubsubservice", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "License :: OSI Approved :: MIT License", + ], + zip_safe=False, + packages=find_packages( + exclude=[ + # Exclude packages that will be covered by PEP420 or nspkg + "azure", + "azure.messaging", + "tests", + "examples", + ] + ), + install_requires=[ + "azure-core<2.0.0,>=1.10.0", + "msrest>=0.6.18", + "cryptography>=2.1.4", + "pyjwt>=1.7.1", + ], + extras_require={ + ":python_version<'3.0'": ["futures", "azure-messaging-nspkg<2.0.0,>=1.0.0"], + ":python_version<'3.5'": ["typing"], + }, +) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/tests/test_send_requests.py b/sdk/signalr/azure-messaging-webpubsubservice/tests/test_send_requests.py new file mode 100644 index 000000000000..7bb7d2277878 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/tests/test_send_requests.py @@ -0,0 +1,15 @@ +from azure.messaging.webpubsubservice import WebPubSubServiceClient +from azure.messaging.webpubsubservice.rest import * + +from azure.core.credentials import AzureKeyCredential + +def test_prepare_request(): + request = prepare_send_to_all('hub', 'hello world', content_type='text/plain') + + assert request.headers['content-type'] == 'text/plain' + assert request.content == 'hello world' + +def test_send_request(): + client = WebPubSubServiceClient('https://www.microsoft.com/api', AzureKeyCredential('abcd')) + request = prepare_send_to_all('hub', 'hello world', content_type='text/plain') + response = client.send_request(request) From dcbd2c89bf7549ed66c9f2cb6c14b6881bb5172f Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Tue, 23 Mar 2021 16:25:25 -0700 Subject: [PATCH 02/33] Update example and README --- sdk/signalr/azure-messaging-webpubsubservice/README.md | 9 +++++++-- .../examples/send_messages.py | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/README.md b/sdk/signalr/azure-messaging-webpubsubservice/README.md index 05c896228ea3..2839d9b86e5c 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/README.md +++ b/sdk/signalr/azure-messaging-webpubsubservice/README.md @@ -34,14 +34,19 @@ In order to interact with the Azure WebPubSub service, you'll need to create an >>> from azure.core.credentials import AzureKeyCredential >>> from azure.messaging.webpubsubservicerest import prepare_send_to_all >>> client = WebPubSubServiceClient(endpoint='', credential=AzureKeyCredential('somesecret')) ->>> request = prepare_send_to_all('default', 'Hello, webpubsub!') +>>> request = prepare_send_to_all('default', { 'Hello': 'webpubsub!' }) >>> request >>> response = client.send_request() >>> response - + >>> response.status_code 202 +>>> with open('send_messages.py', 'r') as f: +>>> request = prepare_send_to_all('ahub', f, content_type='text/plain') +>>> response = client.send_request(request) +>>> print(response) + ``` ## Key concepts diff --git a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py index 4c6456ca239b..fba4ce2a466d 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py @@ -10,7 +10,7 @@ client = WebPubSubServiceClient(endpoint, credential=AzureKeyCredential(api_key)) # Send message to everybody on the given hub... -request = prepare_send_to_all('ahub', 'Hello, all!') +request = prepare_send_to_all('ahub', { 'Hello': 'all!' }) response = client.send_request(request) try: response.raise_for_status() @@ -19,6 +19,11 @@ print('Failed to send message: {}'.format(response)) +with open('send_messages.py', 'r') as f: + request = prepare_send_to_all('ahub', f, content_type='text/plain') + response = client.send_request(request) +print(response) + # Add a user to a group request = prepare_add_user_to_group('ahub', group='someGroup', user_id='me') response = client.send_request(request) From ced586bde7da3e6202f87adcf5df086252054731 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Sat, 27 Mar 2021 13:32:30 -0700 Subject: [PATCH 03/33] Rename preparers to builder, add auth builder. --- .../README.md | 10 +- .../messaging/webpubsubservice/__init__.py | 65 +- .../messaging/webpubsubservice/_policies.py | 11 +- .../messaging/webpubsubservice/core/rest.py | 1 + .../azure/messaging/webpubsubservice/rest.py | 576 ++++++++++++++---- .../dev_requirements.txt | 5 + .../examples/send_messages.py | 40 +- .../azure-messaging-webpubsubservice/setup.py | 1 + .../tests/test_send_requests.py | 33 +- 9 files changed, 587 insertions(+), 155 deletions(-) create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt diff --git a/sdk/signalr/azure-messaging-webpubsubservice/README.md b/sdk/signalr/azure-messaging-webpubsubservice/README.md index 2839d9b86e5c..cb5bf90ceed8 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/README.md +++ b/sdk/signalr/azure-messaging-webpubsubservice/README.md @@ -32,9 +32,9 @@ In order to interact with the Azure WebPubSub service, you'll need to create an ```python >>> from azure.messaging.webpubsubservice import WebPubSubServiceClient >>> from azure.core.credentials import AzureKeyCredential ->>> from azure.messaging.webpubsubservicerest import prepare_send_to_all +>>> from azure.messaging.webpubsubservicerest import build_send_to_all >>> client = WebPubSubServiceClient(endpoint='', credential=AzureKeyCredential('somesecret')) ->>> request = prepare_send_to_all('default', { 'Hello': 'webpubsub!' }) +>>> request = build_send_to_all_request('default', json={ 'Hello': 'webpubsub!' }) >>> request >>> response = client.send_request() @@ -42,8 +42,8 @@ In order to interact with the Azure WebPubSub service, you'll need to create an >>> response.status_code 202 ->>> with open('send_messages.py', 'r') as f: ->>> request = prepare_send_to_all('ahub', f, content_type='text/plain') +>>> with open('file.json', 'r') as f: +>>> request = build_send_to_all_request('ahub', content=f, content_type='application/json') >>> response = client.send_request(request) >>> print(response) @@ -53,7 +53,7 @@ In order to interact with the Azure WebPubSub service, you'll need to create an ### Hubs, groups, connections and users - +TODO: add conceptual docs ## Troubleshooting diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index df973fd5cebb..c267dd20d6a4 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -9,10 +9,11 @@ # regenerated. # -------------------------------------------------------------------------- -__all__ = ["WebPubSubServiceClient"] +__all__ = ["build_authentication_token", "WebPubSubServiceClient"] from typing import TYPE_CHECKING +import jwt import azure.core.pipeline as corepipeline import azure.core.pipeline.policies as corepolicies @@ -20,6 +21,7 @@ # Temporary location for types that eventually graduate to Azure Core from .core import rest as corerest + from ._policies import JwtCredentialPolicy if TYPE_CHECKING: @@ -28,6 +30,62 @@ from typing import Any, List, cast +def build_authentication_token(endpoint, hub, key, **kwargs): + """Build an authentication token for the given endpoint, hub using the provided key. + + :param endpoint: HTTP or HTTPS endpoint for the WebPubSub service instance. + :type endpoint: ~str + :param hub: The hub to give access to. + :type hub: ~str + :param key: Key to sign the token with. + :type key: ~str + :keyword user: Optional user name (subject) for the token. Default is no user. + :type user: ~str + :keyword claims: Additional claims for the token. + :type claims: ~dict + :returns: ~dict containing the web socket endpoint, the token and a url with the generated access token. + :rtype: ~str + + + Example: + >>> build_authentication_token('https://contoso.com/api/webpubsub', hub='theHub', key='123') + { + 'baseUrl': 'wss://contoso.com/api/webpubsub/client/hubs/theHub', + 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ...', + 'url': 'wss://contoso.com/api/webpubsub/client/hubs/theHub?accessToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ...' + } + """ + user = kwargs.pop("user", None) + claims = kwargs.pop("claims", {}) + endpoint = endpoint.lower() + if not endpoint.startswith("http://") and not endpoint.startswith("https://"): + raise ValueError( + "Invalid endpoint: '{}' has unknown scheme - expected 'http://' or 'https://'".format( + endpoint + ) + ) + + # Ensure endpoint has no trailing slash + endpoint = endpoint.rstrip("/") + + # Switch from http(s) to ws(s) scheme + client_endpoint = "ws" + endpoint[4:] + client_url = "{}/client/hubs/{}".format(client_endpoint, hub) + audience = "{}/client/hubs/{}".format(endpoint, hub) + + payload = {"audience": audience, "expiresIn": "1h"} + payload.update(claims) + if user: + payload.setdefault("subject", user) + + token = jwt.encode(payload, key, algorithm="HS256") + return { + "baseUrl": client_url, + "token": token, + "url": "{}?accessToken={}".format(client_url, token), + } + + class WebPubSubServiceClient(object): def __init__(self, endpoint, credential, **kwargs): # type: (str, corecredentials.AzureKeyCredential, Any) -> None @@ -55,7 +113,7 @@ def __init__(self, endpoint, credential, **kwargs): corepolicies.RedirectPolicy(**kwargs), JwtCredentialPolicy(credential, kwargs.get("user", None)), corepolicies.NetworkTraceLoggingPolicy(**kwargs), - ] # type: Any + ] # type: Any self._pipeline = corepipeline.Pipeline( transport, policies, @@ -84,7 +142,8 @@ def send_request(self, request, **kwargs): request.url ) # BUGBUG - should create new request, not mutate the existing one... pipeline_response = self._pipeline.run( - request._internal_request, **kwargs) # pylint: disable=W0212 + request._internal_request, **kwargs # pylint: disable=W0212 + ) return corerest.HttpResponse( request=request, _internal_response=pipeline_response.http_response, diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py index 6020748264ec..a9a38a8710fb 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py @@ -29,6 +29,7 @@ import jwt from azure.core.pipeline.policies import SansIOHTTPPolicy + if typing.TYPE_CHECKING: from azure.core.credentials import AzureKeyCredential from azure.core.pipeline import PipelineRequest @@ -51,6 +52,7 @@ def dst(self, dt): _UTC = _UTC_TZ() + class JwtCredentialPolicy(SansIOHTTPPolicy): NAME_CLAIM_TYPE = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" @@ -77,14 +79,15 @@ def on_request(self, request): request.http_request.headers["Authorization"] = "Bearer " + self._encode( request.http_request.url ) - return super(JwtCredentialPolicy, self).on_request(request) # pylint: disable=R1725 + return super(JwtCredentialPolicy, self).on_request( # pylint: disable=super-with-arguments + request + ) def _encode(self, url): # type: (AzureKeyCredential) -> str data = { "aud": url, - "exp": datetime.datetime.now(tz=_UTC) - + datetime.timedelta(seconds=60), + "exp": datetime.datetime.now(tz=_UTC) + datetime.timedelta(seconds=60), } if self._user: data[self.NAME_CLAIM_TYPE] = self._user @@ -95,5 +98,5 @@ def _encode(self, url): algorithm="HS256", ) if isinstance(encoded, bytes): - encoded = encoded.decode('utf8') + encoded = encoded.decode("utf8") return typing.cast(str, encoded) # jwt's typing is incorrect... diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py index c683818e7bd4..5d5b2ff09956 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py @@ -37,6 +37,7 @@ if TYPE_CHECKING: from typing import Any, Optional, Union, Mapping, Sequence, Tuple + HeaderTypes = Union[Mapping[str, str], Sequence[Tuple[str, str]]] diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py index 8ed0fce1a348..3da4a3b958c6 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py @@ -7,55 +7,62 @@ # -------------------------------------------------------------------------- __all__ = [ - 'prepare_add_connection_to_group', - 'prepare_add_user_to_group', - 'prepare_check_connection_existence', - 'prepare_check_group_existence', - 'prepare_check_permission', - 'prepare_check_user_existence', - 'prepare_check_user_existence_in_group', - 'prepare_close_client_connection', - 'prepare_grant_permission', - 'prepare_healthapi_get_health_status', - 'prepare_remove_connection_from_group', - 'prepare_remove_user_from_all_groups', - 'prepare_remove_user_from_group', - 'prepare_revoke_permission', - 'prepare_send_to_all', - 'prepare_send_to_connection', - 'prepare_send_to_group', - 'prepare_send_to_user' + 'build_add_connection_to_group_request', + 'build_add_user_to_group_request', + 'build_connection_exists_request', + 'build_group_exists_request', + 'build_check_permission_request', + 'build_user_exists_request', + 'build_user_exists_in_group_request', + 'build_close_client_connection_request', + 'build_grant_permission_request', + 'build_healthapi_get_health_status_request', + 'build_remove_connection_from_group_request', + 'build_remove_user_from_all_groups_request', + 'build_remove_user_from_group_request', + 'build_revoke_permission_request', + 'build_send_to_all_request', + 'build_send_to_connection_request', + 'build_send_to_group_request', + 'build_send_to_user_request' ] - from typing import TYPE_CHECKING - from msrest import Serializer -from azure.messaging.webpubsubservice.core.rest import HttpRequest from azure.core.pipeline.transport._base import _format_url_section +from azure.messaging.webpubsubservice.core.rest import HttpRequest if TYPE_CHECKING: # pylint: disable=unused-import,ungrouped-imports from typing import Any, IO, List, Optional, Union, Dict - try: - from typing import Literal - except ImportError: # < 3.8 - from typing_extensions import Literal + from typing_extensions import Literal Permissions = Union[Literal['joinLeaveGroup'], Literal['sendToGroup']] _SERIALIZER = Serializer() -def prepare_healthapi_get_health_status( +def build_healthapi_get_health_status_request( **kwargs # type: Any ): # type: (...) -> HttpRequest + """Get service health status. + + Get service health status. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/health') # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -63,18 +70,39 @@ def prepare_healthapi_get_health_status( method="HEAD", url=url, params=query_parameters, + **kwargs ) -def prepare_send_to_all( +def build_send_to_all_request( hub, # type: str - body, **kwargs # type: Any ): # type: (...) -> HttpRequest + """Broadcast content inside request body to all the connected client connections. + + Broadcast content inside request body to all the connected client connections. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :keyword json: The payload body. + :paramtype json: any + :keyword content: The payload body. + :paramtype content: IO + :keyword excluded: Excluded connection Ids. + :paramtype excluded: list[str] + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ excluded = kwargs.pop('excluded', None) # type: Optional[List[str]] api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] - content_type = kwargs.pop("content_type", "application/octet-stream") + content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/:send') @@ -84,43 +112,50 @@ def prepare_send_to_all( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if excluded is not None: - query_parameters['excluded'] = [ - _SERIALIZER.query("excluded", q, 'str') if q is not None else '' for q in excluded - ] - if api_version is not None: + query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') + if q is not None else '' for q in excluded] + query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') # Construct headers - header_parameters = {} # type: Dict[str, Any] - header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') - - body_content_kwargs = {} # type: Dict[str, Any] - if header_parameters['Content-Type'].split(";")[0] in ['application/octet-stream']: - body_content_kwargs['content'] = body - - elif header_parameters['Content-Type'].split(";")[0] in ['text/plain']: - body_content_kwargs['content'] = body - elif header_parameters['Content-Type'].split(";")[0] in ['application/json']: - body_content_kwargs['content'] = body - + header_parameters = kwargs.pop("headers", {}) # type: Dict[str, Any] + if content_type is not None: + header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') return HttpRequest( method="POST", url=url, params=query_parameters, headers=header_parameters, - **body_content_kwargs + **kwargs ) -def prepare_check_connection_existence( +def build_connection_exists_request( hub, # type: str connection_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Check if the connection with the given connectionId exists. + + Check if the connection with the given connectionId exists. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param connection_id: The connection Id. + :type connection_id: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -132,7 +167,7 @@ def prepare_check_connection_existence( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -140,15 +175,35 @@ def prepare_check_connection_existence( method="HEAD", url=url, params=query_parameters, + **kwargs ) -def prepare_close_client_connection( +def build_close_client_connection_request( hub, # type: str connection_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Close the client connection. + + Close the client connection. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param connection_id: Target connection Id. + :type connection_id: str + :keyword reason: The reason closing the client connection. + :paramtype reason: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ reason = kwargs.pop('reason', None) # type: Optional[str] api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] @@ -161,7 +216,7 @@ def prepare_close_client_connection( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if reason is not None: query_parameters['reason'] = _SERIALIZER.query("reason", reason, 'str') if api_version is not None: @@ -171,18 +226,39 @@ def prepare_close_client_connection( method="DELETE", url=url, params=query_parameters, + **kwargs ) -def prepare_send_to_connection( +def build_send_to_connection_request( hub, # type: str connection_id, # type: str - body, # type: Union[IO, str, Dict] **kwargs # type: Any ): # type: (...) -> HttpRequest + """Send content inside request body to the specific connection. + + Send content inside request body to the specific connection. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param connection_id: The connection Id. + :type connection_id: str + :keyword json: The payload body. + :paramtype json: any + :keyword content: The payload body. + :paramtype content: IO + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] - content_type = kwargs.pop("content_type", "application/octet-stream") + content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}/:send') @@ -193,39 +269,47 @@ def prepare_send_to_connection( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') # Construct headers - header_parameters = {} # type: Dict[str, Any] - header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') - - body_content_kwargs = {} # type: Dict[str, Any] - if header_parameters['Content-Type'].split(";")[0] in ['application/octet-stream']: - body_content_kwargs['content'] = body - - elif header_parameters['Content-Type'].split(";")[0] in ['text/plain']: - body_content_kwargs['content'] = body - elif header_parameters['Content-Type'].split(";")[0] in ['application/json']: - body_content_kwargs['content'] = body - + header_parameters = kwargs.pop("headers", {}) # type: Dict[str, Any] + if content_type is not None: + header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') return HttpRequest( method="POST", url=url, params=query_parameters, headers=header_parameters, - **body_content_kwargs + **kwargs ) -def prepare_check_group_existence( +def build_group_exists_request( hub, # type: str group, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Check if there are any client connections inside the given group. + + Check if there are any client connections inside the given group. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param group: Target group name, which length should be greater than 0 and less than 1025. + :type group: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -237,7 +321,7 @@ def prepare_check_group_existence( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -245,19 +329,42 @@ def prepare_check_group_existence( method="HEAD", url=url, params=query_parameters, + **kwargs ) -def prepare_send_to_group( +def build_send_to_group_request( hub, # type: str group, # type: str - body, # type: Union[IO, str, Dict] **kwargs # type: Any ): # type: (...) -> HttpRequest + """Send content inside request body to a group of connections. + + Send content inside request body to a group of connections. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param group: Target group name, which length should be greater than 0 and less than 1025. + :type group: str + :keyword json: The payload body. + :paramtype json: any + :keyword content: The payload body. + :paramtype content: IO + :keyword excluded: Excluded connection Ids. + :paramtype excluded: list[str] + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ excluded = kwargs.pop('excluded', None) # type: Optional[List[str]] api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] - content_type = kwargs.pop("content_type", "application/octet-stream") + content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/:send') @@ -268,44 +375,53 @@ def prepare_send_to_group( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if excluded is not None: - query_parameters['excluded'] = [ - _SERIALIZER.query("excluded", q, 'str') if q is not None else '' for q in excluded - ] + query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') + if q is not None else '' for q in excluded] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') # Construct headers - header_parameters = {} # type: Dict[str, Any] - header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') - - body_content_kwargs = {} # type: Dict[str, Any] - if header_parameters['Content-Type'].split(";")[0] in ['application/octet-stream']: - body_content_kwargs['content'] = body - - elif header_parameters['Content-Type'].split(";")[0] in ['text/plain']: - body_content_kwargs['content'] = body - elif header_parameters['Content-Type'].split(";")[0] in ['application/json']: - body_content_kwargs['content'] = body - + header_parameters = kwargs.get("headers", {}) # type: Dict[str, Any] + if content_type is not None: + header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') return HttpRequest( method="POST", url=url, params=query_parameters, headers=header_parameters, - **body_content_kwargs + **kwargs ) -def prepare_add_connection_to_group( +def build_add_connection_to_group_request( hub, # type: str group, # type: str connection_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Add a connection to the target group. + + Add a connection to the target group. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param group: Target group name, which length should be greater than 0 and less than 1025. + :type group: str + :param connection_id: Target connection Id. + :type connection_id: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -318,7 +434,7 @@ def prepare_add_connection_to_group( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -326,16 +442,36 @@ def prepare_add_connection_to_group( method="PUT", url=url, params=query_parameters, + **kwargs ) -def prepare_remove_connection_from_group( +def build_remove_connection_from_group_request( hub, # type: str group, # type: str connection_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Remove a connection from the target group. + + Remove a connection from the target group. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param group: Target group name, which length should be greater than 0 and less than 1025. + :type group: str + :param connection_id: Target connection Id. + :type connection_id: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -348,7 +484,7 @@ def prepare_remove_connection_from_group( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -356,15 +492,33 @@ def prepare_remove_connection_from_group( method="DELETE", url=url, params=query_parameters, + **kwargs ) -def prepare_check_user_existence( +def build_user_exists_request( hub, # type: str user_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Check if there are any client connections connected for the given user. + + Check if there are any client connections connected for the given user. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param user_id: Target user Id. + :type user_id: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -376,7 +530,7 @@ def prepare_check_user_existence( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -384,19 +538,39 @@ def prepare_check_user_existence( method="HEAD", url=url, params=query_parameters, + **kwargs ) -def prepare_send_to_user( +def build_send_to_user_request( hub, # type: str user_id, # type: str - body, # type: Union[IO, str, Dict] - **kwargs # type: Any ): # type: (...) -> HttpRequest + """Send content inside request body to the specific user. + + Send content inside request body to the specific user. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param user_id: The user Id. + :type user_id: str + :keyword json: The payload body. + :paramtype json: any + :keyword content: The payload body. + :paramtype content: IO + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] - content_type = kwargs.pop("content_type", "application/octet-stream") + content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/:send') @@ -407,40 +581,50 @@ def prepare_send_to_user( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') # Construct headers - header_parameters = {} # type: Dict[str, Any] - header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') - - body_content_kwargs = {} # type: Dict[str, Any] - if header_parameters['Content-Type'].split(";")[0] in ['application/octet-stream']: - body_content_kwargs['content'] = body - - elif header_parameters['Content-Type'].split(";")[0] in ['text/plain']: - body_content_kwargs['content'] = body - elif header_parameters['Content-Type'].split(";")[0] in ['application/json']: - body_content_kwargs['content'] = body - + header_parameters = kwargs.pop("headers", {}) # type: Dict[str, Any] + if content_type is not None: + header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') return HttpRequest( method="POST", url=url, params=query_parameters, headers=header_parameters, - **body_content_kwargs + **kwargs ) -def prepare_check_user_existence_in_group( +def build_user_exists_in_group_request( hub, # type: str group, # type: str user_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Check whether a user exists in the target group. + + Check whether a user exists in the target group. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param group: Target group name, which length should be greater than 0 and less than 1025. + :type group: str + :param user_id: Target user Id. + :type user_id: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -453,7 +637,7 @@ def prepare_check_user_existence_in_group( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -461,16 +645,36 @@ def prepare_check_user_existence_in_group( method="HEAD", url=url, params=query_parameters, + **kwargs ) -def prepare_add_user_to_group( +def build_add_user_to_group_request( hub, # type: str group, # type: str user_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Add a user to the target group. + + Add a user to the target group. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param group: Target group name, which length should be greater than 0 and less than 1025. + :type group: str + :param user_id: Target user Id. + :type user_id: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -483,7 +687,7 @@ def prepare_add_user_to_group( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -491,16 +695,36 @@ def prepare_add_user_to_group( method="PUT", url=url, params=query_parameters, + **kwargs ) -def prepare_remove_user_from_group( +def build_remove_user_from_group_request( hub, # type: str group, # type: str user_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Remove a user from the target group. + + Remove a user from the target group. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param group: Target group name, which length should be greater than 0 and less than 1025. + :type group: str + :param user_id: Target user Id. + :type user_id: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -513,7 +737,7 @@ def prepare_remove_user_from_group( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -521,15 +745,33 @@ def prepare_remove_user_from_group( method="DELETE", url=url, params=query_parameters, + **kwargs ) -def prepare_remove_user_from_all_groups( +def build_remove_user_from_all_groups_request( hub, # type: str user_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Remove a user from all groups. + + Remove a user from all groups. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param user_id: Target user Id. + :type user_id: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] # Construct URL @@ -541,7 +783,7 @@ def prepare_remove_user_from_all_groups( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') @@ -549,16 +791,41 @@ def prepare_remove_user_from_all_groups( method="DELETE", url=url, params=query_parameters, + **kwargs ) -def prepare_grant_permission( +def build_grant_permission_request( hub, # type: str permission, # type: Permissions connection_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Grant permission to the connection. + + Grant permission to the connection. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param permission: The permission: current supported actions are joinLeaveGroup and + sendToGroup. + :type permission: str or ~Permissions + :param connection_id: Target connection Id. + :type connection_id: str + :keyword target_name: Optional. If not set, grant the permission to all the targets. If set, + grant the permission to the specific target. The meaning of the target depends on the specific + permission. + :paramtype target_name: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ target_name = kwargs.pop('target_name', None) # type: Optional[str] api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] @@ -572,7 +839,7 @@ def prepare_grant_permission( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if target_name is not None: query_parameters['targetName'] = _SERIALIZER.query("target_name", target_name, 'str') if api_version is not None: @@ -582,16 +849,41 @@ def prepare_grant_permission( method="PUT", url=url, params=query_parameters, + **kwargs ) -def prepare_revoke_permission( +def build_revoke_permission_request( hub, # type: str permission, # type: Permissions connection_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Revoke permission for the connection. + + Revoke permission for the connection. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name, which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param permission: The permission: current supported actions are joinLeaveGroup and + sendToGroup. + :type permission: ~Permissions + :param connection_id: Target connection Id. + :type connection_id: str + :keyword target_name: Optional. If not set, revoke the permission for all targets. If set, + revoke the permission for the specific target. The meaning of the target depends on the + specific permission. + :paramtype target_name: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ target_name = kwargs.pop('target_name', None) # type: Optional[str] api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] @@ -605,7 +897,7 @@ def prepare_revoke_permission( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if target_name is not None: query_parameters['targetName'] = _SERIALIZER.query("target_name", target_name, 'str') if api_version is not None: @@ -615,16 +907,41 @@ def prepare_revoke_permission( method="DELETE", url=url, params=query_parameters, + **kwargs ) -def prepare_check_permission( +def build_check_permission_request( hub, # type: str permission, # type: Permissions connection_id, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest + """Check if a connection has permission to the specified action. + + Check if a connection has permission to the specified action. + + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + + :param hub: Target hub name,q which should start with alphabetic characters and only contain + alpha-numeric characters or underscore. + :type hub: str + :param permission: The permission: current supported actions are joinLeaveGroup and + sendToGroup. + :type permission: ~Permissions + :param connection_id: Target connection Id. + :type connection_id: str + :keyword target_name: Optional. If not set, get the permission for all targets. If set, get the + permission for the specific target. The meaning of the target depends on the specific + permission. + :paramtype target_name: str + :keyword api_version: Api Version. + :paramtype api_version: str + :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. + :rtype: ~azure.core.rest.HttpRequest + """ target_name = kwargs.pop('target_name', None) # type: Optional[str] api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] @@ -638,7 +955,7 @@ def prepare_check_permission( url = _format_url_section(url, **path_format_arguments) # Construct parameters - query_parameters = {} # type: Dict[str, Any] + query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if target_name is not None: query_parameters['targetName'] = _SERIALIZER.query("target_name", target_name, 'str') if api_version is not None: @@ -648,4 +965,5 @@ def prepare_check_permission( method="HEAD", url=url, params=query_parameters, + **kwargs ) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt b/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt new file mode 100644 index 000000000000..9938821516f1 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt @@ -0,0 +1,5 @@ +-e ../../../tools/azure-devtools +-e ../../../tools/azure-sdk-tools +../../core/azure-core +-e ../../identity/azure-identity +aiohttp>=3.0; python_version >= '3.5' diff --git a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py index fba4ce2a466d..099827cf6c60 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py @@ -1,3 +1,5 @@ +import io +import logging import os from azure.core.credentials import AzureKeyCredential @@ -7,10 +9,12 @@ endpoint = os.environ['ENDPOINT'] api_key = os.environ['API_KEY'] -client = WebPubSubServiceClient(endpoint, credential=AzureKeyCredential(api_key)) +LOG = logging.basicConfig(level=logging.DEBUG) + +client = WebPubSubServiceClient(endpoint, credential=AzureKeyCredential(api_key), tracing_enabled=True) # Send message to everybody on the given hub... -request = prepare_send_to_all('ahub', { 'Hello': 'all!' }) +request = build_send_to_all_request('ahub', json={ 'Hello': 'all!' }) response = client.send_request(request) try: response.raise_for_status() @@ -19,27 +23,45 @@ print('Failed to send message: {}'.format(response)) -with open('send_messages.py', 'r') as f: - request = prepare_send_to_all('ahub', f, content_type='text/plain') - response = client.send_request(request) +request = build_send_to_all_request('ahub', content='hello, text!', content_type='text/plain') +print(request.headers.items()) +response = client.send_request(request) +print(response) + + +request = build_send_to_all_request('ahub', content=io.BytesIO(b'{ "hello": "world" }'), content_type='application/json') +response = client.send_request(request) print(response) # Add a user to a group -request = prepare_add_user_to_group('ahub', group='someGroup', user_id='me') +request = build_add_user_to_group_request('ahub', group='someGroup', user_id='me') response = client.send_request(request) try: response.raise_for_status() except: print('Failed to add user to group: {}'.format(response)) +request = build_user_exists_in_group_request('ahub', 'someGroup', 'me') +response = client.send_request(request) + +request = build_add_connection_to_group_request('ahub', 'someGroup', '7') +response = client.send_request(request) +print(response.content) # Add a connection to a group -request = prepare_add_connection_to_group(hub='ahub', group='thegroup', connection_id='`7') +request = build_add_connection_to_group_request(hub='ahub', group='someGroup', connection_id='7') response = client.send_request(request) print(response) # Check if a group exists -request = prepare_check_group_existence(hub='ahub', group='missing') +request = build_connection_exists_request(hub='ahub', connection_id='missing') response = client.send_request(request) -print(response) +if response.status_code == 404: + print("no such group") +else: + print('The group exists!') + +request = build_grant_permission_request('ahub', 'sendToGroup', '7') +response = client.send_request(request) +print(response.content) \ No newline at end of file diff --git a/sdk/signalr/azure-messaging-webpubsubservice/setup.py b/sdk/signalr/azure-messaging-webpubsubservice/setup.py index 914125ece0e8..0e712cc8b797 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/setup.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/setup.py @@ -67,6 +67,7 @@ "msrest>=0.6.18", "cryptography>=2.1.4", "pyjwt>=1.7.1", + "typing_extensions", ], extras_require={ ":python_version<'3.0'": ["futures", "azure-messaging-nspkg<2.0.0,>=1.0.0"], diff --git a/sdk/signalr/azure-messaging-webpubsubservice/tests/test_send_requests.py b/sdk/signalr/azure-messaging-webpubsubservice/tests/test_send_requests.py index 7bb7d2277878..4f402e2e719b 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/tests/test_send_requests.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/tests/test_send_requests.py @@ -1,15 +1,38 @@ +import io + from azure.messaging.webpubsubservice import WebPubSubServiceClient from azure.messaging.webpubsubservice.rest import * from azure.core.credentials import AzureKeyCredential -def test_prepare_request(): - request = prepare_send_to_all('hub', 'hello world', content_type='text/plain') +def test_build_text_request(): + request = build_send_to_all_request('hub', content='hello world', content_type='text/plain') assert request.headers['content-type'] == 'text/plain' assert request.content == 'hello world' -def test_send_request(): +def test_build_json_request(): + client = WebPubSubServiceClient('https://www.microsoft.com/api', AzureKeyCredential('abcd')) + request = build_send_to_all_request('hub', json={'hello': 'world'}) + assert request.headers['content-type'] == 'application/json' + +def test_build_stream_request(): + stream = io.BytesIO(b'1234') + client = WebPubSubServiceClient('https://www.microsoft.com/api', AzureKeyCredential('abcd')) + request = build_send_to_all_request('hub', content=stream, content_type='application/octet-stream') + assert request.headers['content-type'] == 'application/octet-stream' + +def test_build_stream_json_request(): + stream = io.BytesIO(b'{ "hello": "web" }') + client = WebPubSubServiceClient('https://www.microsoft.com/api', AzureKeyCredential('abcd')) + request = build_send_to_all_request('hub', content=stream, content_type='application/octet-json') + assert request.headers['content-type'] == 'application/octet-json' + +def test_build_send_message_exclude(): + stream = io.BytesIO(b'{ "hello": "web" }') client = WebPubSubServiceClient('https://www.microsoft.com/api', AzureKeyCredential('abcd')) - request = prepare_send_to_all('hub', 'hello world', content_type='text/plain') - response = client.send_request(request) + request = build_send_to_all_request('hub', content=stream, content_type='application/octet-json', excluded=['a', 'b', 'c']) + assert 'excluded=a&' in request.url + assert 'excluded=b&' in request.url + assert 'excluded=c' in request.url + assert 'excluded=d' not in request.url From 96eed1d98ba84d706839018d2d531562415dc817 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Sat, 27 Mar 2021 14:01:05 -0700 Subject: [PATCH 04/33] Added license.txt and setup.cfg --- .../LICENSE.txt | 21 +++++++++++++++++++ .../setup.cfg | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/LICENSE.txt create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/setup.cfg diff --git a/sdk/signalr/azure-messaging-webpubsubservice/LICENSE.txt b/sdk/signalr/azure-messaging-webpubsubservice/LICENSE.txt new file mode 100644 index 000000000000..0313a903d76c --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/sdk/signalr/azure-messaging-webpubsubservice/setup.cfg b/sdk/signalr/azure-messaging-webpubsubservice/setup.cfg new file mode 100644 index 000000000000..3c6e79cf31da --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 From 23dbcb71bb1264c16651557a97920339e73d8e7b Mon Sep 17 00:00:00 2001 From: "Liangying.Wei" Date: Tue, 6 Apr 2021 20:13:46 +0800 Subject: [PATCH 05/33] Update the sign algo to generate the expected jwt --- .../azure/messaging/webpubsubservice/__init__.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index c267dd20d6a4..74af0c503424 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -19,6 +19,8 @@ import azure.core.pipeline.policies as corepolicies import azure.core.pipeline.transport as coretransport +from datetime import datetime, timedelta + # Temporary location for types that eventually graduate to Azure Core from .core import rest as corerest @@ -39,6 +41,8 @@ def build_authentication_token(endpoint, hub, key, **kwargs): :type hub: ~str :param key: Key to sign the token with. :type key: ~str + :keyword ttl: Optional ttl timedelta for the token. Default is 1 hour. + :type ttl: ~datetime.timedelta :keyword user: Optional user name (subject) for the token. Default is no user. :type user: ~str :keyword claims: Additional claims for the token. @@ -52,11 +56,12 @@ def build_authentication_token(endpoint, hub, key, **kwargs): { 'baseUrl': 'wss://contoso.com/api/webpubsub/client/hubs/theHub', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ...', - 'url': 'wss://contoso.com/api/webpubsub/client/hubs/theHub?accessToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ...' + 'url': 'wss://contoso.com/api/webpubsub/client/hubs/theHub?access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ...' } """ user = kwargs.pop("user", None) claims = kwargs.pop("claims", {}) + ttl = kwargs.pop("ttl", timedelta(hours=1)) endpoint = endpoint.lower() if not endpoint.startswith("http://") and not endpoint.startswith("https://"): raise ValueError( @@ -72,17 +77,17 @@ def build_authentication_token(endpoint, hub, key, **kwargs): client_endpoint = "ws" + endpoint[4:] client_url = "{}/client/hubs/{}".format(client_endpoint, hub) audience = "{}/client/hubs/{}".format(endpoint, hub) - - payload = {"audience": audience, "expiresIn": "1h"} + + payload = {"aud": audience, "iat": datetime.utcnow(), "exp": datetime.utcnow() + ttl} payload.update(claims) if user: - payload.setdefault("subject", user) + payload.setdefault("sub", user) token = jwt.encode(payload, key, algorithm="HS256") return { "baseUrl": client_url, "token": token, - "url": "{}?accessToken={}".format(client_url, token), + "url": "{}?access_token={}".format(client_url, token), } From 425458f30e23e32dc65c2a034b250ed463c5a555 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Mon, 12 Apr 2021 21:03:26 -0700 Subject: [PATCH 06/33] Remove use of datetime.utcnow --- .../messaging/webpubsubservice/__init__.py | 20 +++++---- .../messaging/webpubsubservice/_policies.py | 22 ++-------- .../messaging/webpubsubservice/_utils.py | 44 +++++++++++++++++++ 3 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_utils.py diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index 74af0c503424..968b45ccaf74 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -11,6 +11,7 @@ __all__ = ["build_authentication_token", "WebPubSubServiceClient"] +from datetime import datetime, timedelta from typing import TYPE_CHECKING import jwt @@ -19,12 +20,12 @@ import azure.core.pipeline.policies as corepolicies import azure.core.pipeline.transport as coretransport -from datetime import datetime, timedelta # Temporary location for types that eventually graduate to Azure Core from .core import rest as corerest from ._policies import JwtCredentialPolicy +from ._utils import UTC as _UTC if TYPE_CHECKING: import azure.core.credentials as corecredentials @@ -45,10 +46,10 @@ def build_authentication_token(endpoint, hub, key, **kwargs): :type ttl: ~datetime.timedelta :keyword user: Optional user name (subject) for the token. Default is no user. :type user: ~str - :keyword claims: Additional claims for the token. - :type claims: ~dict + :keyword roles: Roles for the token. + :type roles: typing.List[str]. Default is no roles. :returns: ~dict containing the web socket endpoint, the token and a url with the generated access token. - :rtype: ~str + :rtype: ~dict Example: @@ -60,8 +61,8 @@ def build_authentication_token(endpoint, hub, key, **kwargs): } """ user = kwargs.pop("user", None) - claims = kwargs.pop("claims", {}) ttl = kwargs.pop("ttl", timedelta(hours=1)) + roles = kwargs.pop('roles', []) endpoint = endpoint.lower() if not endpoint.startswith("http://") and not endpoint.startswith("https://"): raise ValueError( @@ -77,11 +78,12 @@ def build_authentication_token(endpoint, hub, key, **kwargs): client_endpoint = "ws" + endpoint[4:] client_url = "{}/client/hubs/{}".format(client_endpoint, hub) audience = "{}/client/hubs/{}".format(endpoint, hub) - - payload = {"aud": audience, "iat": datetime.utcnow(), "exp": datetime.utcnow() + ttl} - payload.update(claims) + + payload = {"aud": audience, "iat": datetime.now(tz=_UTC), "exp": datetime.now(tz=_UTC) + ttl} if user: - payload.setdefault("sub", user) + payload["sub"] = user + if roles: + payload["Roles"] = roles token = jwt.encode(payload, key, algorithm="HS256") return { diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py index a9a38a8710fb..37e7933ff907 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py @@ -30,29 +30,13 @@ from azure.core.pipeline.policies import SansIOHTTPPolicy +from ._utils import UTC + if typing.TYPE_CHECKING: from azure.core.credentials import AzureKeyCredential from azure.core.pipeline import PipelineRequest -class _UTC_TZ(datetime.tzinfo): - """from https://docs.python.org/2/library/datetime.html#tzinfo-objects""" - - ZERO = datetime.timedelta(0) - - def utcoffset(self, dt): - return self.__class__.ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return self.__class__.ZERO - - -_UTC = _UTC_TZ() - - class JwtCredentialPolicy(SansIOHTTPPolicy): NAME_CLAIM_TYPE = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" @@ -87,7 +71,7 @@ def _encode(self, url): # type: (AzureKeyCredential) -> str data = { "aud": url, - "exp": datetime.datetime.now(tz=_UTC) + datetime.timedelta(seconds=60), + "exp": datetime.datetime.now(tz=UTC) + datetime.timedelta(seconds=60), } if self._user: data[self.NAME_CLAIM_TYPE] = self._user diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_utils.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_utils.py new file mode 100644 index 000000000000..84c5f51a56ca --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_utils.py @@ -0,0 +1,44 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- + +import datetime + +class _UTC_TZ(datetime.tzinfo): + """from https://docs.python.org/2/library/datetime.html#tzinfo-objects""" + + ZERO = datetime.timedelta(0) + + def utcoffset(self, dt): + return self.__class__.ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return self.__class__.ZERO + + +UTC = _UTC_TZ() From c2c1fcb9b91c17333af1d191911e138cfe2487c0 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Mon, 12 Apr 2021 21:07:57 -0700 Subject: [PATCH 07/33] Fix spelling mistake in roles claim --- .../azure/messaging/webpubsubservice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index 968b45ccaf74..0d473b4646b7 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -83,7 +83,7 @@ def build_authentication_token(endpoint, hub, key, **kwargs): if user: payload["sub"] = user if roles: - payload["Roles"] = roles + payload["roles"] = roles token = jwt.encode(payload, key, algorithm="HS256") return { From 84290d5d6b6d9b7229f8b30c82f8c4e1b3e0c066 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Wed, 14 Apr 2021 09:29:45 -0700 Subject: [PATCH 08/33] Fix misspelled role --- .../azure/messaging/webpubsubservice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index 0d473b4646b7..f57a1f16ceb2 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -83,7 +83,7 @@ def build_authentication_token(endpoint, hub, key, **kwargs): if user: payload["sub"] = user if roles: - payload["roles"] = roles + payload["role"] = roles token = jwt.encode(payload, key, algorithm="HS256") return { From c301168ff7962cc10616ae84086e515bc7a9000a Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Tue, 20 Apr 2021 22:27:02 -0700 Subject: [PATCH 09/33] fixed typos in the README examples --- sdk/signalr/azure-messaging-webpubsubservice/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/README.md b/sdk/signalr/azure-messaging-webpubsubservice/README.md index cb5bf90ceed8..07bfc485d26f 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/README.md +++ b/sdk/signalr/azure-messaging-webpubsubservice/README.md @@ -32,12 +32,12 @@ In order to interact with the Azure WebPubSub service, you'll need to create an ```python >>> from azure.messaging.webpubsubservice import WebPubSubServiceClient >>> from azure.core.credentials import AzureKeyCredential ->>> from azure.messaging.webpubsubservicerest import build_send_to_all +>>> from azure.messaging.webpubsubservice.rest import build_send_to_all_request >>> client = WebPubSubServiceClient(endpoint='', credential=AzureKeyCredential('somesecret')) >>> request = build_send_to_all_request('default', json={ 'Hello': 'webpubsub!' }) >>> request ->>> response = client.send_request() +>>> response = client.send_request(request) >>> response >>> response.status_code @@ -53,8 +53,6 @@ In order to interact with the Azure WebPubSub service, you'll need to create an ### Hubs, groups, connections and users -TODO: add conceptual docs - ## Troubleshooting ### Logging From cba5a7bf53536e5a52a3e201e72eed2bf3e20caf Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Tue, 20 Apr 2021 22:27:32 -0700 Subject: [PATCH 10/33] Added from_connection_string constructor, fixed UA string --- .../messaging/webpubsubservice/__init__.py | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index f57a1f16ceb2..f069edba3aed 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -16,6 +16,7 @@ import jwt +import azure.core.credentials as corecredentials import azure.core.pipeline as corepipeline import azure.core.pipeline.policies as corepolicies import azure.core.pipeline.transport as coretransport @@ -23,14 +24,14 @@ # Temporary location for types that eventually graduate to Azure Core from .core import rest as corerest - +from ._version import VERSION as _VERSION from ._policies import JwtCredentialPolicy from ._utils import UTC as _UTC if TYPE_CHECKING: - import azure.core.credentials as corecredentials from azure.core.pipeline.policies import HTTPPolicy, SansIOHTTPPolicy - from typing import Any, List, cast + from typing import Any, List, cast, Type, TypeVar + ClientType = TypeVar('ClientType', bound='WebPubSubServiceClient') def build_authentication_token(endpoint, hub, key, **kwargs): @@ -111,6 +112,7 @@ def __init__(self, endpoint, credential, **kwargs): transport = kwargs.pop("transport", None) or coretransport.RequestsTransport( **kwargs ) + kwargs.setdefault('sdk_moniker', 'messaging-webpubsubservice/{}'.format(_VERSION)) policies = [ corepolicies.HeadersPolicy(**kwargs), corepolicies.UserAgentPolicy(**kwargs), @@ -126,6 +128,35 @@ def __init__(self, endpoint, credential, **kwargs): policies, ) # type: corepipeline.Pipeline + + @classmethod + def from_connection_string(cls, connection_string, **kwargs): + # type: (Type[ClientType], str, Any) -> ClientType + for invalid_keyword_arg in ('endpoint', 'accesskey'): + if invalid_keyword_arg in kwargs: + raise TypeError('Unknown argument {}'.format(invalid_keyword_arg)) + + for segment in connection_string.split(";"): + if '=' in segment: + key, value = segment.split('=', maxsplit=1) + key = key.lower() + if key == 'version': + # The name in the connection string != the name for the constructor. + # Let's map it to whatthe constructor actually wants... + key = 'api_version' + kwargs[key] = value + else: + raise ValueError("Malformed connection string - expected 'key=value', got {}".format(segment)) + + if 'endpoint' not in kwargs: + raise ValueError("connection_string missing 'endpoint' field") + + if 'accesskey' not in kwargs: + raise ValueError("connection_string missing 'accesskey' field") + + kwargs['credential'] = corecredentials.AzureKeyCredential(kwargs.pop('accesskey')) + return cls(**kwargs) + def __repr__(self): return " endpoint:'{}'".format(self.endpoint) From c5e689002a9ce597b7ad4caa49e817f12b00f77d Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Wed, 21 Apr 2021 21:38:39 -0700 Subject: [PATCH 11/33] update readme.md to follow conventions --- .../README.md | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/README.md b/sdk/signalr/azure-messaging-webpubsubservice/README.md index 07bfc485d26f..913b81a7d651 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/README.md +++ b/sdk/signalr/azure-messaging-webpubsubservice/README.md @@ -1,5 +1,30 @@ # AzureWebPubSub Service client library for Python +[Azure Web PubSub Service](https://aka.ms/awps/doc) is a service that enables you to build real-time messaging web applications using WebSockets and the publish-subscribe pattern. Any platform supporting WebSocket APIs can connect to the service easily, e.g. web pages, mobile applications, edge devices, etc. The service manages the WebSocket connections for you and allows up to 100K \*concurrent connections. It provides powerful APIs for you to manage these clients and deliver real-time messages. + +Any scenario that requires real-time publish-subscribe messaging between server and clients or among clients, can use Azure Web PubSub service. Traditional real-time features that often require polling from server or submitting HTTP requests, can also use Azure Web PubSub service. + +We list some examples that are good to use Azure Web PubSub service: + +- **High frequency data updates:** gaming, voting, polling, auction. +- **Live dashboards and monitoring:** company dashboard, financial market data, instant sales update, multi-player game leader board, and IoT monitoring. +- **Cross-platform live chat:** live chat room, chat bot, on-line customer support, real-time shopping assistant, messenger, in-game chat, and so on. +- **Real-time location on map:** logistic tracking, delivery status tracking, transportation status updates, GPS apps. +- **Real-time targeted ads:** personalized real-time push ads and offers, interactive ads. +- **Collaborative apps:** coauthoring, whiteboard apps and team meeting software. +- **Push instant notifications:** social network, email, game, travel alert. +- **Real-time broadcasting:** live audio/video broadcasting, live captioning, translating, events/news broadcasting. +- **IoT and connected devices:** real-time IoT metrics, remote control, real-time status, and location tracking. +- **Automation:** real-time trigger from upstream events. + +Use the client library to: + +- Send messages to hubs and groups. +- Send messages to particular users and connections. +- Organize users and connections into groups. +- Close connections +- Grant/revoke/check permissions for an existing connection + [Source code](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/signalr/azure-messaging-webpubsubservice) | [Package (Pypi)][package] | [API reference documentation](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/signalr/azure-messaging-webpubsubservice) | [Product documentation][webpubsubservice_docs] ## Getting started @@ -12,8 +37,8 @@ python -m pip install azure-messaging-webpubsubservice #### Prequisites -* Python 2.7, or 3.6 or later is required to use this package. -* You need an [Azure subscription][azure_sub], and a [Azure WebPubSub service instance][webpubsubservice] to use this package. +- Python 2.7, or 3.6 or later is required to use this package. +- You need an [Azure subscription][azure_sub], and a [Azure WebPubSub service instance][webpubsubservice] to use this package. ### Authenticating the client @@ -51,7 +76,25 @@ In order to interact with the Azure WebPubSub service, you'll need to create an ## Key concepts -### Hubs, groups, connections and users +### Hub + +Hub is a logical set of connections. All connections to Web PubSub connect to a specific hub. Messages that are broadcast to the hub are dispatched to all connections to that hub. For example, hub can be used for different applications, different applications can share one Azure Web PubSub service by using different hub names. + +### Group + +Group allow broadcast messages to a subset of connections to the hub. You can add and remove users and connections as needed. A client can join multiple groups, and a group can contain multiple clients. + +### User + +Connections to Web PubSub can belong to one user. A user might have multiple connections, for example when a single user is connected across multiple devices or multiple browser tabs. + +### Connection + +Connections, represented by a connection id, represent an individual websocket connection to the Web PubSub service. Connection id is always unique. + +### Message + +A message is either a UTF-8 encoded string or raw binary data. ## Troubleshooting From ae8df54fccd738cb14e7ab246eac2db8d97ea90d Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Thu, 22 Apr 2021 09:03:21 -0700 Subject: [PATCH 12/33] Pylint fix --- .../azure/messaging/webpubsubservice/_policies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py index 37e7933ff907..85924e6b5f72 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py @@ -63,7 +63,7 @@ def on_request(self, request): request.http_request.headers["Authorization"] = "Bearer " + self._encode( request.http_request.url ) - return super(JwtCredentialPolicy, self).on_request( # pylint: disable=super-with-arguments + return super(JwtCredentialPolicy, self).on_request( # pylint: disable=R1725 request ) From 5833035eba6bc724bdd2992c00ee0ea869996057 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Thu, 22 Apr 2021 09:26:50 -0700 Subject: [PATCH 13/33] Readme fixes, typing_extension removed as dependency --- sdk/signalr/azure-messaging-webpubsubservice/README.md | 8 ++++---- .../azure-messaging-webpubsubservice/dev_requirements.txt | 1 + sdk/signalr/azure-messaging-webpubsubservice/setup.py | 1 - 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/README.md b/sdk/signalr/azure-messaging-webpubsubservice/README.md index 913b81a7d651..b92a9a831179 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/README.md +++ b/sdk/signalr/azure-messaging-webpubsubservice/README.md @@ -38,7 +38,8 @@ python -m pip install azure-messaging-webpubsubservice #### Prequisites - Python 2.7, or 3.6 or later is required to use this package. -- You need an [Azure subscription][azure_sub], and a [Azure WebPubSub service instance][webpubsubservice] to use this package. +- You need an [Azure subscription][azure_sub], and a [Azure WebPubSub service instance][webpubsubservice_docs] to use this package. +- An existing Azure Web PubSub service instance. ### Authenticating the client @@ -94,7 +95,7 @@ Connections, represented by a connection id, represent an individual websocket c ### Message -A message is either a UTF-8 encoded string or raw binary data. +A message is either a UTF-8 encoded string, json or raw binary data. ## Troubleshooting @@ -129,12 +130,11 @@ see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments. -[webpubsubservice_docs]: https://docs.microsoft.com/azure/azure-messaging-webpubsubservice/ +[webpubsubservice_docs]: https://aka.ms/awps/doc [azure_cli]: https://docs.microsoft.com/cli/azure [azure_sub]: https://azure.microsoft.com/free/ [webpubsubservice_client_class]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py [package]: https://pypi.org/project/azure-messaging-webpubsubservice/ -[webpubsubservice]: https://azure.microsoft.com/services/webpubsubservice/ [default_cred_ref]: https://aka.ms/azsdk-python-identity-default-cred-ref [cla]: https://cla.microsoft.com [code_of_conduct]: https://opensource.microsoft.com/codeofconduct/ diff --git a/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt b/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt index 9938821516f1..b3effab89e4d 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt +++ b/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt @@ -3,3 +3,4 @@ ../../core/azure-core -e ../../identity/azure-identity aiohttp>=3.0; python_version >= '3.5' +typing_extensions>=3.7.2 diff --git a/sdk/signalr/azure-messaging-webpubsubservice/setup.py b/sdk/signalr/azure-messaging-webpubsubservice/setup.py index 0e712cc8b797..914125ece0e8 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/setup.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/setup.py @@ -67,7 +67,6 @@ "msrest>=0.6.18", "cryptography>=2.1.4", "pyjwt>=1.7.1", - "typing_extensions", ], extras_require={ ":python_version<'3.0'": ["futures", "azure-messaging-nspkg<2.0.0,>=1.0.0"], From 5645f0b6e5776f53f996f25bec19e6ca0ca24898 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Thu, 22 Apr 2021 13:34:44 -0700 Subject: [PATCH 14/33] Suppressed warning from wrong version of pylint. Fixed --- .../azure/messaging/webpubsubservice/_policies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py index 85924e6b5f72..ef79dd66c480 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py @@ -63,7 +63,7 @@ def on_request(self, request): request.http_request.headers["Authorization"] = "Bearer " + self._encode( request.http_request.url ) - return super(JwtCredentialPolicy, self).on_request( # pylint: disable=R1725 + return super(JwtCredentialPolicy, self).on_request( request ) From e41354bc0e512750e6f054f4d2385ee58e355e7d Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Thu, 22 Apr 2021 22:18:00 -0700 Subject: [PATCH 15/33] Update to later codegen --- .../messaging/webpubsubservice/__init__.py | 67 +- .../messaging/webpubsubservice/core/rest.py | 275 ------- .../webpubsubservice/core/rest/__init__.py | 47 ++ .../webpubsubservice/core/rest/_rest.py | 752 ++++++++++++++++++ .../webpubsubservice/core/rest/_rest_py3.py | 691 ++++++++++++++++ .../azure/messaging/webpubsubservice/rest.py | 305 ++++--- .../examples/send_messages.py | 18 +- 7 files changed, 1689 insertions(+), 466 deletions(-) delete mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index f069edba3aed..33504145b4c4 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -11,6 +11,7 @@ __all__ = ["build_authentication_token", "WebPubSubServiceClient"] +from copy import deepcopy from datetime import datetime, timedelta from typing import TYPE_CHECKING @@ -102,7 +103,7 @@ def __init__(self, endpoint, credential, **kwargs): :param endpoint: Endpoint to connect to. :type endpoint: ~str :param credential: Credentials to use to connect to endpoint. - :type credential: ~azure.core.credentials.AzureKeyCredentials + :type credential: ~azure.core.credentials.AzureKeyCredential :keyword api_version: Api version to use when communicating with the service. :type api_version: str :keyword user: User to connect as. Optional. @@ -121,6 +122,7 @@ def __init__(self, endpoint, credential, **kwargs): corepolicies.CustomHookPolicy(**kwargs), corepolicies.RedirectPolicy(**kwargs), JwtCredentialPolicy(credential, kwargs.get("user", None)), + corepolicies.ContentDecodePolicy(**kwargs), corepolicies.NetworkTraceLoggingPolicy(**kwargs), ] # type: Any self._pipeline = corepipeline.Pipeline( @@ -132,6 +134,12 @@ def __init__(self, endpoint, credential, **kwargs): @classmethod def from_connection_string(cls, connection_string, **kwargs): # type: (Type[ClientType], str, Any) -> ClientType + """Create a new WebPubSubServiceClient from a connection string. + + :param connection_string: Connection string + :type connection_string: ~str + :rtype: WebPubSubServiceClient + """ for invalid_keyword_arg in ('endpoint', 'accesskey'): if invalid_keyword_arg in kwargs: raise TypeError('Unknown argument {}'.format(invalid_keyword_arg)) @@ -145,8 +153,9 @@ def from_connection_string(cls, connection_string, **kwargs): # Let's map it to whatthe constructor actually wants... key = 'api_version' kwargs[key] = value - else: - raise ValueError("Malformed connection string - expected 'key=value', got {}".format(segment)) + elif segment: + raise ValueError("Malformed connection string - expected 'key=value', found segment '{}' in '{}'" + .format(segment, connection_string)) if 'endpoint' not in kwargs: raise ValueError("connection_string missing 'endpoint' field") @@ -165,24 +174,44 @@ def _format_url(self, url): assert self.endpoint[-1] != "/", "My endpoint should not have a trailing slash" return "/".join([self.endpoint, url.lstrip("/")]) - def send_request(self, request, **kwargs): + def send_request(self, http_request, **kwargs): # type: (corerest.HttpRequest, Any) -> corerest.HttpResponse """Runs the network request through the client's chained policies. - :param request: The network request you want to make. Required. - :type request: ~corerest.HttpRequest - :keyword bool stream: Whether the response payload will be streamed. Defaults to False - :return: The response of your network call. - :rtype: ~corerest.HttpResponse + We have helper methods to create requests specific to this service in `azure.messaging.webpubsub.rest`. + Use these helper methods to create the request you pass to this method. See our example below: + + >>> from azure.messaging.webpubsub.rest import build_healthapi_get_health_status_request + >>> request = build_healthapi_get_health_status_request(api_version) + + >>> response = client.send_request(request) + + + For more information on this code flow, see https://aka.ms/azsdk/python/llcwiki + + For advanced cases, you can also create your own :class:`~azure.messaging.webpubsub.core.rest.HttpRequest` + and pass it in. + + :param http_request: The network request you want to make. Required. + :type http_request: ~azure.messaging.webpubsub.core.rest.HttpRequest + :keyword bool stream_response: Whether the response payload will be streamed. Defaults to False. + :return: The response of your network call. Does not do error handling on your response. + :rtype: ~azure.messaging.webpubsub.core.rest.HttpResponse """ - kwargs.setdefault("stream", False) - request.url = self._format_url( - request.url - ) # BUGBUG - should create new request, not mutate the existing one... - pipeline_response = self._pipeline.run( - request._internal_request, **kwargs # pylint: disable=W0212 - ) - return corerest.HttpResponse( - request=request, - _internal_response=pipeline_response.http_response, + request_copy = deepcopy(http_request) + request_copy.url = self._format_url(request_copy.url) + + # can't do StreamCOntextManager yet. This client doesn't have a pipeline client, + # StreamContextManager requires a pipeline client. WIll look more into it + # if kwargs.pop("stream_response", False): + # return corerest._StreamContextManager( + # client=self._client, + # request=request_copy, + # ) + pipeline_response = self._pipeline.run(request_copy._internal_request, **kwargs) + response = corerest.HttpResponse( + status_code=pipeline_response.http_response.status_code, + request=request_copy, + _internal_response=pipeline_response.http_response ) + return response diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py deleted file mode 100644 index 5d5b2ff09956..000000000000 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py +++ /dev/null @@ -1,275 +0,0 @@ -# -------------------------------------------------------------------------- -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# The MIT License (MIT) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the ""Software""), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -# IN THE SOFTWARE. -# -# -------------------------------------------------------------------------- - -__all__ = ["HttpRequest", "HttpResponse"] - -import json as jsonlib -import xml.etree.ElementTree as ET -from typing import TYPE_CHECKING - -from azure.core.pipeline.transport import ( - HttpRequest as _PipelineTransportHttpRequest, - HttpResponse as _PipelineTransportHttpResponse, -) - -if TYPE_CHECKING: - from typing import Any, Optional, Union, Mapping, Sequence, Tuple - - HeaderTypes = Union[Mapping[str, str], Sequence[Tuple[str, str]]] - - -def _is_stream(content): - return isinstance(content, (str, bytes)) or any( - hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] - ) - - -class HttpRequest(object): - """Represents an HTTP request. - - :param method: HTTP method (GET, HEAD, etc.) - :type method: str or ~azure.core.protocol.HttpVerbs - :param str url: The url for your request - :keyword params: Query parameters to be mapped into your URL. Your input - should be a mapping or sequence of query name to query value(s). - :paramtype params: mapping or sequence - :keyword headers: HTTP headers you want in your request. Your input should - be a mapping or sequence of header name to header value. - :paramtype headers: mapping or sequence - :keyword dict data: Form data you want in your request body. Use for form-encoded data, i.e. - HTML forms. - :keyword any json: A JSON serializable object. We handle JSON-serialization for your - object, so use this for more complicated data structures than `data`. - :keyword files: Files you want to in your request body. Use for uploading files with - multipart encoding. Your input should be a mapping or sequence of file name to file content. - Use the `data` kwarg in addition if you want to include non-file data files as part of your request. - :paramtype files: mapping or sequence - :keyword content: Content you want in your request body. Think of it as the kwarg you should input - if your data doesn't fit into `json`, `data`, or `files`. Accepts a bytes type, or a generator - that yields bytes. - :paramtype content: str or bytes or iterable[bytes] or asynciterable[bytes] - """ - - def __init__(self, method, url, **kwargs): - # type: (str, str, Any) -> None - - data = kwargs.pop("data", None) - content = kwargs.pop("content", None) - json = kwargs.pop("json", None) - files = kwargs.pop("files", None) - - self._internal_request = _PipelineTransportHttpRequest( - method=method, - url=url, - headers=kwargs.pop("headers", None), - ) - params = kwargs.pop("params", None) - - if params: - self._internal_request.format_parameters(params) - if data is not None: - self._internal_request.set_formdata_body(data) - if content is not None: - content_type = self._internal_request.headers.get("Content-Type") - if _is_stream(content): - self._internal_request.set_streamed_data_body(content) - elif isinstance(content, ET.Element): - self._internal_request.set_xml_body(content) - elif content_type and content_type.startswith("text/"): - self._internal_request.set_text_body(content) - else: - self._internal_request.data = content - if json is not None: - self._internal_request.set_json_body(json) - if not self._internal_request.headers.get("Content-Type"): - self._internal_request.headers["Content-Type"] = "application/json" - if files is not None: - self._internal_request.set_formdata_body(files) - - if not self._internal_request.headers.get("Content-Length"): - try: - # set content length header if possible - self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) # type: ignore - except TypeError: - pass - self.method = self._internal_request.method - if kwargs: - raise TypeError( - "You have passed in kwargs '{}' that are not valid kwargs.".format( - "', '".join(list(kwargs.keys())) - ) - ) - - @property - def url(self): - # type: (...) -> str - return self._internal_request.url - - @url.setter - def url(self, val): - # type: (str) -> None - self._internal_request.url = val - - @property - def method(self): - # type: (...) -> str - return self._internal_request.method - - @method.setter - def method(self, val): - # type: (str) -> None - self._internal_request.method = val - - @property - def headers(self): - return self._internal_request.headers - - @property - def content(self): - return self._internal_request.data - - def __repr__(self): - return "".format(self.method, self.url) - - def __deepcopy__(self, memo=None): - return self._internal_request.__deepcopy__(memo) - - -class _HttpResponseBase(object): - """Base class for HttpResponse and AsyncHttpResponse. - :param int status_code: Status code of the response. - :keyword headers: Response headers - :paramtype headers: dict[str, any] - :keyword str text: The response content as a string - :keyword any json: JSON content - :keyword stream: Streamed response - :paramtype stream: bytes or iterator of bytes - :keyword callable on_close: Any callable you want to cal - when closing your HttpResponse - :keyword history: If redirection, history of all redirection - that resulted in this response. - :paramtype history: list[~azure.core.protocol.HttpResponse] - """ - - def __init__(self, **kwargs): - # type: (Any) -> None - self._internal_response = kwargs.pop( - "_internal_response" - ) # type: _PipelineTransportHttpResponse - self.request = kwargs.pop("request") - self._encoding = "" - - @property - def status_code(self): - # type: (...) -> int - """Returns the status code of the response""" - return self._internal_response.status_code or -1 - - @status_code.setter - def status_code(self, val): - # type: (int) -> None - """Set the status code of the response""" - self._internal_response.status_code = val - - @property - def headers(self): - # type: (...) -> HeaderTypes - """Returns the response headers""" - return self._internal_response.headers - - @property - def reason(self): - # type: (...) -> str - """Returns the reason phrase for the response""" - return self._internal_response.reason or "" - - @property - def content(self): - # type: (...) -> bytes - """Returns the response content in bytes""" - raise NotImplementedError() - - @property - def url(self): - # type: (...) -> str - """Returns the URL that resulted in this response""" - return self._internal_response.request.url - - @property - def encoding(self): - # type: (...) -> Optional[str] - """Returns the response encoding. By default, is specified - by the response Content-Type header. - """ - return self._encoding - - @encoding.setter - def encoding(self, value): - # type: (str) -> None - """Sets the response encoding""" - self._encoding = value - - @property - def text(self): - # type: (...) -> str - """Returns the response body as a string""" - return self._internal_response.text(self.encoding) - - def json(self): - # type: (...) -> Any - """Returns the whole body as a json object. - - :return: The JSON deserialized response body - :rtype: any - :raises json.decoder.JSONDecodeError or ValueError (in python 2.7) if object is not JSON decodable: - """ - return jsonlib.loads(self._internal_response.text(self.encoding)) - - def raise_for_status(self): - # type: () -> None - """Raises an HttpResponseError if the response has an error status code. - - If response is good, does nothing. - """ - self._internal_response.raise_for_status() - - def __repr__(self): - # type: (...) -> str - return repr(self._internal_response) - - -class HttpResponse(_HttpResponseBase): - @property - def content(self): - # type: (...) -> bytes - return self._internal_response.body() - - -class AsyncHttpResponse(_HttpResponseBase): - @property - def content(self): - # type: (...) -> bytes - return self._internal_response.body() diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py new file mode 100644 index 000000000000..09575fb1cab9 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py @@ -0,0 +1,47 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- +try: + from ._rest_py3 import HttpRequest + from ._rest_py3 import HttpResponse + from ._rest_py3 import AsyncHttpResponse + from ._rest_py3 import _StreamContextManager + from ._rest_py3 import _AsyncStreamContextManager + + __all__ = [ + "HttpRequest", + "HttpResponse", + "AsyncHttpResponse", + "_StreamContextManager", + "_AsyncStreamContextManager", + ] +except (SyntaxError, ImportError): + from ._rest import HttpRequest + from ._rest import HttpResponse + + __all__ = [ + "HttpRequest", + "HttpResponse", + ] diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py new file mode 100644 index 000000000000..89e50b9154c7 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py @@ -0,0 +1,752 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + +
+ + + + + + + + + +
+
+
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py new file mode 100644 index 000000000000..fd86cbf0711b --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py @@ -0,0 +1,691 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- + +__all__ = [ + "HttpRequest", + "HttpResponse", +] +import asyncio +import os +import binascii +import codecs +import cgi +import json +from enum import Enum +import xml.etree.ElementTree as ET +from typing import ( + Any, + AsyncIterable, + IO, + Iterable, Iterator, + Optional, + Type, + Union, + Mapping, + Sequence, + Tuple, + List, +) +from abc import abstractmethod + +################################### TYPES SECTION ######################### + +ByteStream = Union[Iterable[bytes], AsyncIterable[bytes]] +PrimitiveData = Optional[Union[str, int, float, bool]] + + +ParamsType = Union[ + Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]], + List[Tuple[str, PrimitiveData]] +] + +HeadersType = Union[ + Mapping[str, str], + Sequence[Tuple[str, str]] +] + +ContentType = Union[str, bytes, ByteStream] + +FileContent = Union[str, bytes, IO[str], IO[bytes]] +FileType = Union[ + Tuple[Optional[str], FileContent], +] + +FilesType = Union[ + Mapping[str, FileType], + Sequence[Tuple[str, FileType]] +] + +from azure.core.pipeline import Pipeline +from azure.core.pipeline.transport import ( + HttpRequest as _PipelineTransportHttpRequest, +) + +from azure.core.pipeline.transport._base import ( + _HttpResponseBase as _PipelineTransportHttpResponseBase +) + +from azure.core._pipeline_client import PipelineClient as _PipelineClient +from azure.core._pipeline_client_async import AsyncPipelineClient as _AsyncPipelineClient + +class HttpVerbs(str, Enum): + GET = "GET" + PUT = "PUT" + POST = "POST" + HEAD = "HEAD" + PATCH = "PATCH" + DELETE = "DELETE" + MERGE = "MERGE" + +########################### UTILS SECTION ################################# + +def _is_stream_or_str_bytes(content: Any) -> bool: + return isinstance(content, (str, bytes)) or any( + hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] + ) + +def _lookup_encoding(encoding: str) -> bool: + # including check for whether encoding is known taken from httpx + try: + codecs.lookup(encoding) + return True + except LookupError: + return False + +def _set_content_length_header(header_name: str, header_value: str, internal_request: _PipelineTransportHttpRequest) -> None: + valid_methods = ["put", "post", "patch"] + content_length_headers = ["Content-Length", "Transfer-Encoding"] + if ( + internal_request.method.lower() in valid_methods and + not any([c for c in content_length_headers if c in internal_request.headers]) + ): + internal_request.headers[header_name] = header_value + +def _set_content_type_header(header_value: str, internal_request: _PipelineTransportHttpRequest) -> None: + if not internal_request.headers.get("Content-Type"): + internal_request.headers["Content-Type"] = header_value + +def _set_content_body(content: ContentType, internal_request: _PipelineTransportHttpRequest) -> None: + headers = internal_request.headers + content_type = headers.get("Content-Type") + if _is_stream_or_str_bytes(content): + # stream will be bytes / str, or iterator of bytes / str + internal_request.set_streamed_data_body(content) + if isinstance(content, str) and content: + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_type_header("text/plain", internal_request) + elif isinstance(content, bytes) and content: + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_type_header("application/octet-stream", internal_request) + elif isinstance(content, (Iterable, AsyncIterable)): + _set_content_length_header("Transfer-Encoding", "chunked", internal_request) + _set_content_type_header("application/octet-stream", internal_request) + elif isinstance(content, ET.Element): + # XML body + internal_request.set_xml_body(content) + _set_content_type_header("application/xml", internal_request) + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + elif content_type and content_type.startswith("text/"): + # Text body + internal_request.set_text_body(content) + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + else: + # Other body + internal_request.data = content + internal_request.headers = headers + +def _set_body( + content: ContentType, data: dict, files: Any, json_body: Any, internal_request: _PipelineTransportHttpRequest +) -> None: + if data is not None and not isinstance(data, dict): + content = data + data = None + if content is not None: + _set_content_body(content, internal_request) + elif json_body is not None: + internal_request.set_json_body(json_body) + _set_content_type_header("application/json", internal_request) + elif files is not None: + internal_request.set_formdata_body(files) + # if you don't supply your content type, we'll create a boundary for you with multipart/form-data + boundary = binascii.hexlify(os.urandom(16)).decode("ascii") # got logic from httpx, thanks httpx! + # _set_content_type_header("multipart/form-data; boundary={}".format(boundary), internal_request) + elif data: + _set_content_type_header("application/x-www-form-urlencoded", internal_request) + internal_request.set_formdata_body(data) + # need to set twice because Content-Type is being popped in set_formdata_body + # don't want to risk changing pipeline.transport, so doing twice here + _set_content_type_header("application/x-www-form-urlencoded", internal_request) + +def _parse_lines_from_text(text): + # largely taken from httpx's LineDecoder code + lines = [] + last_chunk_of_text = "" + while text: + text_length = len(text) + for idx in range(text_length): + curr_char = text[idx] + next_char = None if idx == len(text) - 1 else text[idx + 1] + if curr_char == "\n": + lines.append(text[: idx + 1]) + text = text[idx + 1: ] + break + if curr_char == "\r" and next_char == "\n": + # if it ends with \r\n, we only do \n + lines.append(text[:idx] + "\n") + text = text[idx + 2:] + break + if curr_char == "\r" and next_char is not None: + # if it's \r then a normal character, we switch \r to \n + lines.append(text[:idx] + "\n") + text = text[idx + 1:] + break + if next_char is None: + text = "" + last_chunk_of_text += text + break + if last_chunk_of_text.endswith("\r"): + # if ends with \r, we switch \r to \n + lines.append(last_chunk_of_text[:-1] + "\n") + elif last_chunk_of_text: + lines.append(last_chunk_of_text) + return lines + + +class _StreamContextManagerBase: + def __init__( + self, + client: Union[_PipelineClient, _AsyncPipelineClient], + request: "HttpRequest", + **kwargs + ): + """Used so we can treat stream requests and responses as a context manager. + + In Autorest, we only return a `StreamContextManager` if users pass in `stream_response` True + + Actually sends request when we enter the context manager, closes response when we exit. + + Heavily inspired from httpx, we want the same behavior for it to feel consistent for users + """ + self.client = client + self.request = request + self.kwargs = kwargs + + @abstractmethod + def close(self): + ... + +class _StreamContextManager(_StreamContextManagerBase): + def __enter__(self) -> "HttpResponse": + """Actually make the call only when we enter. For sync stream_response calls""" + pipeline_transport_response = self.client._pipeline.run( + self.request._internal_request, + stream=True, + **self.kwargs + ).http_response + self.response = HttpResponse( + request=self.request, + _internal_response=pipeline_transport_response + ) + return self.response + + def __exit__(self, *args): + """Close our stream connection. For sync calls""" + self.response.__exit__(*args) + + def close(self): + self.response.close() + +class _AsyncStreamContextManager(_StreamContextManagerBase): + async def __aenter__(self) -> "AsyncHttpResponse": + """Actually make the call only when we enter. For async stream_response calls.""" + if not isinstance(self.client, _AsyncPipelineClient): + raise TypeError( + "Only sync calls should enter here. If you mean to do a sync call, " + "make sure to use 'with' instead." + ) + pipeline_transport_response = (await self.client._pipeline.run( + self.request._internal_request, + stream=True, + **self.kwargs + )).http_response + self.response = AsyncHttpResponse( + request=self.request, + _internal_response=pipeline_transport_response + ) + return self.response + + async def __aexit__(self, *args): + await self.response.__aexit__(*args) + + async def close(self): + await self.response.close() + +################################## CLASSES ###################################### + +class HttpRequest: + """Represents an HTTP request. + + :param method: HTTP method (GET, HEAD, etc.) + :type method: str or ~azure.core.protocol.HttpVerbs + :param str url: The url for your request + :keyword params: Query parameters to be mapped into your URL. Your input + should be a mapping or sequence of query name to query value(s). + :paramtype params: mapping or sequence + :keyword headers: HTTP headers you want in your request. Your input should + be a mapping or sequence of header name to header value. + :paramtype headers: mapping or sequence + :keyword any json: A JSON serializable object. We handle JSON-serialization for your + object, so use this for more complicated data structures than `data`. + :keyword content: Content you want in your request body. Think of it as the kwarg you should input + if your data doesn't fit into `json`, `data`, or `files`. Accepts a bytes type, or a generator + that yields bytes. + :paramtype content: str or bytes or iterable[bytes] or asynciterable[bytes] + :keyword dict data: Form data you want in your request body. Use for form-encoded data, i.e. + HTML forms. + :keyword files: Files you want to in your request body. Use for uploading files with + multipart encoding. Your input should be a mapping or sequence of file name to file content. + Use the `data` kwarg in addition if you want to include non-file data files as part of your request. + :paramtype files: mapping or sequence + :ivar str url: The URL this request is against. + :ivar str method: The method type of this request. + :ivar headers: The HTTP headers you passed in to your request + :vartype headers: mapping or sequence + :ivar bytes content: The content passed in for the request + """ + + def __init__( + self, + method: str, + url: str, + *, + params: Optional[ParamsType] = None, + headers: Optional[HeadersType] = None, + json: Any = None, + content: Optional[ContentType] = None, + data: Optional[dict] = None, + files: Optional[FilesType] = None, + **kwargs + ): + # type: (str, str, Any) -> None + + self._internal_request = kwargs.pop("_internal_request", _PipelineTransportHttpRequest( + method=method, + url=url, + headers=headers, + )) + + if params: + self._internal_request.format_parameters(params) + + _set_body( + content=content, + data=data, + files=files, + json_body=json, + internal_request=self._internal_request + ) + + if kwargs: + raise TypeError( + "You have passed in kwargs '{}' that are not valid kwargs.".format( + "', '".join(list(kwargs.keys())) + ) + ) + + def _set_content_length_header(self) -> None: + method_check = self._internal_request.method.lower() in ["put", "post", "patch"] + content_length_unset = "Content-Length" not in self._internal_request.headers + if method_check and content_length_unset: + self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) + + @property + def url(self) -> str: + return self._internal_request.url + + @url.setter + def url(self, val: str) -> None: + self._internal_request.url = val + + @property + def method(self) -> str: + return self._internal_request.method + + @property + def headers(self) -> HeadersType: + return self._internal_request.headers + + @property + def content(self) -> Any: + """Gets the request content. + """ + return self._internal_request.data or self._internal_request.files + + def __repr__(self) -> str: + return self._internal_request.__repr__() + + def __deepcopy__(self, memo=None) -> "HttpRequest": + return HttpRequest( + self.method, + self.url, + _internal_request=self._internal_request.__deepcopy__(memo) + ) + +class _HttpResponseBase: + """Base class for HttpResponse and AsyncHttpResponse. + + :keyword request: The request that resulted in this response. + :paramtype request: ~azure.core.rest.HttpRequest + :ivar int status_code: The status code of this response + :ivar headers: The response headers + :vartype headers: dict[str, any] + :ivar str reason: The reason phrase for this response + :ivar bytes content: The response content in bytes + :ivar str url: The URL that resulted in this response + :ivar str encoding: The response encoding. Is settable, by default + is the response Content-Type header + :ivar str text: The response body as a string. + :ivar request: The request that resulted in this response. + :vartype request: ~azure.core.rest.HttpRequest + :ivar str content_type: The content type of the response + :ivar bool is_closed: Whether the network connection has been closed yet + :ivar bool is_stream_consumed: When getting a stream response, checks + whether the stream has been fully consumed + :ivar int num_bytes_downloaded: The number of bytes in your stream that + have been downloaded + """ + + def __init__( + self, + *, + request: HttpRequest, + **kwargs + ): + self._internal_response = kwargs.pop("_internal_response") # type: _PipelineTransportHttpResponseBase + self._request = request + self.is_closed = False + self.is_stream_consumed = False + self._num_bytes_downloaded = 0 + + @property + def status_code(self) -> int: + """Returns the status code of the response""" + return self._internal_response.status_code + + @status_code.setter + def status_code(self, val: int) -> None: + """Set the status code of the response""" + self._internal_response.status_code = val + + @property + def headers(self) -> HeadersType: + """Returns the response headers""" + return self._internal_response.headers + + @property + def reason(self) -> str: + """Returns the reason phrase for the response""" + return self._internal_response.reason + + @property + def content(self) -> bytes: + """Returns the response content in bytes""" + raise NotImplementedError() + + @property + def url(self) -> str: + """Returns the URL that resulted in this response""" + return self._internal_response.request.url + + @property + def encoding(self) -> str: + """Returns the response encoding. By default, is specified + by the response Content-Type header. + """ + + try: + return self._encoding + except AttributeError: + return self._get_charset_encoding() + + def _get_charset_encoding(self) -> str: + content_type = self.headers.get("Content-Type") + + if not content_type: + return None + _, params = cgi.parse_header(content_type) + encoding = params.get('charset') # -> utf-8 + if encoding is None or not _lookup_encoding(encoding): + return None + return encoding + + @encoding.setter + def encoding(self, value: str) -> None: + # type: (str) -> None + """Sets the response encoding""" + self._encoding = value + + @property + def text(self) -> str: + """Returns the response body as a string""" + return self._internal_response.text(encoding=self.encoding) + + @property + def request(self) -> HttpRequest: + if self._request: + return self._request + raise RuntimeError( + "You are trying to access the 'request', but there is no request associated with this HttpResponse" + ) + + @request.setter + def request(self, val: HttpRequest) -> None: + self._request = val + + @property + def content_type(self) -> Optional[str]: + """Content Type of the response""" + return self._internal_response.content_type or self.headers.get("Content-Type") + + @property + def num_bytes_downloaded(self) -> int: + """See how many bytes of your stream response have been downloaded""" + return self._num_bytes_downloaded + + def json(self) -> Any: + """Returns the whole body as a json object. + + :return: The JSON deserialized response body + :rtype: any + :raises json.decoder.JSONDecodeError or ValueError (in python 2.7) if object is not JSON decodable: + """ + return json.loads(self.text) + + def raise_for_status(self) -> None: + """Raises an HttpResponseError if the response has an error status code. + + If response is good, does nothing. + """ + return self._internal_response.raise_for_status() + + def __repr__(self) -> str: + content_type_str = ( + ", Content-Type: {}".format(self.content_type) if self.content_type else "" + ) + return "<{}: {} {}{}>".format( + type(self).__name__, self.status_code, self.reason, content_type_str + ) + + def _validate_streaming_access(self) -> None: + if self.is_closed: + raise TypeError("Can not iterate over stream, it is closed.") + if self.is_stream_consumed: + raise TypeError("Can not iterate over stream, it has been fully consumed") + +class HttpResponse(_HttpResponseBase): + + @property + def content(self): + # type: (...) -> bytes + try: + return self._content + except AttributeError: + raise TypeError("You have not read in the response's bytes yet. Call response.read() first.") + + def close(self) -> None: + self.is_closed = True + self._internal_response.internal_response.close() + + def __exit__(self, *args) -> None: + self._internal_response.internal_response.__exit__(*args) + + def read(self) -> bytes: + """ + Read the response's bytes. + + """ + try: + return self._content + except AttributeError: + self._validate_streaming_access() + self._content = ( + self._internal_response.body() or + b"".join(self.iter_raw()) + ) + self._close_stream() + return self._content + + def iter_bytes(self, chunk_size: int = None) -> Iterator[bytes]: + """Iterate over the bytes in the response stream + """ + try: + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), chunk_size): + yield self._content[i: i + chunk_size] + + except AttributeError: + for raw_bytes in self.iter_raw(chunk_size=chunk_size): + yield raw_bytes + + def iter_text(self, chunk_size: int = None) -> Iterator[str]: + """Iterate over the response text + """ + for byte in self.iter_bytes(chunk_size): + text = byte.decode(self.encoding or "utf-8") + yield text + + def iter_lines(self, chunk_size: int = None) -> Iterator[str]: + for text in self.iter_text(chunk_size): + lines = _parse_lines_from_text(text) + for line in lines: + yield line + + def _close_stream(self) -> None: + self.is_stream_consumed = True + self.close() + + def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: + """Iterate over the raw response bytes + """ + self._validate_streaming_access() + stream_download = self._internal_response.stream_download(None, chunk_size=chunk_size) + for raw_bytes in stream_download: + self._num_bytes_downloaded += len(raw_bytes) + yield raw_bytes + + self._close_stream() + + +class AsyncHttpResponse(_HttpResponseBase): + + @property + def content(self) -> bytes: + try: + return self._content + except AttributeError: + raise TypeError("You have not read in the response's bytes yet. Call response.read() first.") + + async def _close_stream(self) -> None: + self.is_stream_consumed = True + await self.close() + + async def read(self) -> bytes: + """ + Read the response's bytes. + + """ + try: + return self._content + except AttributeError: + self._validate_streaming_access() + await self._internal_response.load_body() + self._content = self._internal_response._body + await self._close_stream() + return self._content + + async def iter_bytes(self, chunk_size: int = None) -> Iterator[bytes]: + """Iterate over the bytes in the response stream + """ + try: + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), chunk_size): + yield self._content[i: i + chunk_size] + + except AttributeError: + async for raw_bytes in self.iter_raw(chunk_size=chunk_size): + yield raw_bytes + + async def iter_text(self, chunk_size: int = None) -> Iterator[str]: + """Iterate over the response text + """ + async for byte in self.iter_bytes(chunk_size): + text = byte.decode(self.encoding or "utf-8") + yield text + + async def iter_lines(self, chunk_size: int = None) -> Iterator[str]: + async for text in self.iter_text(chunk_size): + lines = _parse_lines_from_text(text) + for line in lines: + yield line + + async def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: + """Iterate over the raw response bytes + """ + self._validate_streaming_access() + stream_download = self._internal_response.stream_download(None, chunk_size=chunk_size) + async for raw_bytes in stream_download: + self._num_bytes_downloaded += len(raw_bytes) + yield raw_bytes + + await self._close_stream() + + async def close(self) -> None: + self.is_closed = True + self._internal_response.internal_response.close() + await asyncio.sleep(0) + + async def __aexit__(self, *args) -> None: + await self._internal_response.internal_response.__aexit__(*args) \ No newline at end of file diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py index 3da4a3b958c6..3ec77a5e1375 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py @@ -13,7 +13,6 @@ 'build_group_exists_request', 'build_check_permission_request', 'build_user_exists_request', - 'build_user_exists_in_group_request', 'build_close_client_connection_request', 'build_grant_permission_request', 'build_healthapi_get_health_status_request', @@ -48,15 +47,15 @@ def build_healthapi_get_health_status_request( Get service health status. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/health') @@ -83,40 +82,45 @@ def build_send_to_all_request( Broadcast content inside request body to all the connected client connections. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. :type hub: str :keyword json: The payload body. - :paramtype json: any + :paramtype json: Any :keyword content: The payload body. :paramtype content: IO :keyword excluded: Excluded connection Ids. :paramtype excluded: list[str] :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest + + Example: + .. code-block:: python + + # JSON input template you can fill out and use as your `json` input. + json = "Any (optional)" """ excluded = kwargs.pop('excluded', None) # type: Optional[List[str]] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/:send') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), } url = _format_url_section(url, **path_format_arguments) # Construct parameters query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if excluded is not None: - query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') - if q is not None else '' for q in excluded] - + query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') if q is not None else '' for q in excluded] + if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') # Construct headers @@ -143,7 +147,7 @@ def build_connection_exists_request( Check if the connection with the given connectionId exists. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -152,17 +156,17 @@ def build_connection_exists_request( :type connection_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -189,7 +193,7 @@ def build_close_client_connection_request( Close the client connection. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -200,18 +204,18 @@ def build_close_client_connection_request( :paramtype reason: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ reason = kwargs.pop('reason', None) # type: Optional[str] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -240,7 +244,7 @@ def build_send_to_connection_request( Send content inside request body to the specific connection. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -248,23 +252,29 @@ def build_send_to_connection_request( :param connection_id: The connection Id. :type connection_id: str :keyword json: The payload body. - :paramtype json: any + :paramtype json: Any :keyword content: The payload body. :paramtype content: IO :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest + + Example: + .. code-block:: python + + # JSON input template you can fill out and use as your `json` input. + json = "Any (optional)" """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}/:send') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -297,7 +307,7 @@ def build_group_exists_request( Check if there are any client connections inside the given group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -306,17 +316,17 @@ def build_group_exists_request( :type group: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -343,7 +353,7 @@ def build_send_to_group_request( Send content inside request body to a group of connections. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -351,39 +361,44 @@ def build_send_to_group_request( :param group: Target group name, which length should be greater than 0 and less than 1025. :type group: str :keyword json: The payload body. - :paramtype json: any + :paramtype json: Any :keyword content: The payload body. :paramtype content: IO :keyword excluded: Excluded connection Ids. :paramtype excluded: list[str] :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest + + Example: + .. code-block:: python + + # JSON input template you can fill out and use as your `json` input. + json = "Any (optional)" """ excluded = kwargs.pop('excluded', None) # type: Optional[List[str]] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/:send') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), } url = _format_url_section(url, **path_format_arguments) # Construct parameters query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if excluded is not None: - query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') - if q is not None else '' for q in excluded] + query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') if q is not None else '' for q in excluded] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') # Construct headers - header_parameters = kwargs.get("headers", {}) # type: Dict[str, Any] + header_parameters = kwargs.pop("headers", {}) # type: Dict[str, Any] if content_type is not None: header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') @@ -407,7 +422,7 @@ def build_add_connection_to_group_request( Add a connection to the target group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -418,18 +433,18 @@ def build_add_connection_to_group_request( :type connection_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -457,7 +472,7 @@ def build_remove_connection_from_group_request( Remove a connection from the target group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -468,18 +483,18 @@ def build_remove_connection_from_group_request( :type connection_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -506,7 +521,7 @@ def build_user_exists_request( Check if there are any client connections connected for the given user. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -515,17 +530,17 @@ def build_user_exists_request( :type user_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -552,7 +567,7 @@ def build_send_to_user_request( Send content inside request body to the specific user. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -560,23 +575,29 @@ def build_send_to_user_request( :param user_id: The user Id. :type user_id: str :keyword json: The payload body. - :paramtype json: any + :paramtype json: Any :keyword content: The payload body. :paramtype content: IO :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest + + Example: + .. code-block:: python + + # JSON input template you can fill out and use as your `json` input. + json = "Any (optional)" """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/:send') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -599,56 +620,6 @@ def build_send_to_user_request( ) -def build_user_exists_in_group_request( - hub, # type: str - group, # type: str - user_id, # type: str - **kwargs # type: Any -): - # type: (...) -> HttpRequest - """Check whether a user exists in the target group. - - Check whether a user exists in the target group. - - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. - - :param hub: Target hub name, which should start with alphabetic characters and only contain - alpha-numeric characters or underscore. - :type hub: str - :param group: Target group name, which length should be greater than 0 and less than 1025. - :type group: str - :param user_id: Target user Id. - :type user_id: str - :keyword api_version: Api Version. - :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest - """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] - - # Construct URL - url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') - path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), - } - url = _format_url_section(url, **path_format_arguments) - - # Construct parameters - query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] - if api_version is not None: - query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') - - return HttpRequest( - method="HEAD", - url=url, - params=query_parameters, - **kwargs - ) - - def build_add_user_to_group_request( hub, # type: str group, # type: str @@ -660,7 +631,7 @@ def build_add_user_to_group_request( Add a user to the target group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -671,18 +642,18 @@ def build_add_user_to_group_request( :type user_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -710,7 +681,7 @@ def build_remove_user_from_group_request( Remove a user from the target group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -721,18 +692,18 @@ def build_remove_user_from_group_request( :type user_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -759,7 +730,7 @@ def build_remove_user_from_all_groups_request( Remove a user from all groups. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -768,17 +739,17 @@ def build_remove_user_from_all_groups_request( :type user_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -806,7 +777,7 @@ def build_grant_permission_request( Grant permission to the connection. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -822,19 +793,19 @@ def build_grant_permission_request( :paramtype target_name: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ target_name = kwargs.pop('target_name', None) # type: Optional[str] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), 'permission': _SERIALIZER.url("permission", permission, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -864,14 +835,14 @@ def build_revoke_permission_request( Revoke permission for the connection. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. :type hub: str :param permission: The permission: current supported actions are joinLeaveGroup and sendToGroup. - :type permission: ~Permissions + :type permission: str or ~Permissions :param connection_id: Target connection Id. :type connection_id: str :keyword target_name: Optional. If not set, revoke the permission for all targets. If set, @@ -880,19 +851,19 @@ def build_revoke_permission_request( :paramtype target_name: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ target_name = kwargs.pop('target_name', None) # type: Optional[str] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), 'permission': _SERIALIZER.url("permission", permission, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -922,9 +893,9 @@ def build_check_permission_request( Check if a connection has permission to the specified action. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. - :param hub: Target hub name,q which should start with alphabetic characters and only contain + :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. :type hub: str :param permission: The permission: current supported actions are joinLeaveGroup and @@ -938,19 +909,19 @@ def build_check_permission_request( :paramtype target_name: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ target_name = kwargs.pop('target_name', None) # type: Optional[str] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), 'permission': _SERIALIZER.url("permission", permission, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -966,4 +937,4 @@ def build_check_permission_request( url=url, params=query_parameters, **kwargs - ) + ) \ No newline at end of file diff --git a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py index 099827cf6c60..92a6605bed3b 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py @@ -13,8 +13,10 @@ client = WebPubSubServiceClient(endpoint, credential=AzureKeyCredential(api_key), tracing_enabled=True) + # Send message to everybody on the given hub... -request = build_send_to_all_request('ahub', json={ 'Hello': 'all!' }) +request = build_send_to_all_request('skalman', json={ 'Hello': 'all!' }) +print(request.headers) response = client.send_request(request) try: response.raise_for_status() @@ -41,11 +43,9 @@ except: print('Failed to add user to group: {}'.format(response)) -request = build_user_exists_in_group_request('ahub', 'someGroup', 'me') -response = client.send_request(request) - request = build_add_connection_to_group_request('ahub', 'someGroup', '7') response = client.send_request(request) +response.read() print(response.content) # Add a connection to a group @@ -62,6 +62,14 @@ print('The group exists!') +request = build_grant_permission_request('skalman', 'sendToGroup', '7') +response = client.send_request(request) +response.read() +print(response.content) + + + request = build_grant_permission_request('ahub', 'sendToGroup', '7') response = client.send_request(request) -print(response.content) \ No newline at end of file +print(response.content) + From 8d3dad890891fadaf3396911b6160f306fd56cd6 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Thu, 22 Apr 2021 22:22:21 -0700 Subject: [PATCH 16/33] Added async client --- .../azure/messaging/webpubsubservice/aio.py | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py new file mode 100644 index 000000000000..7c1921139afb --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py @@ -0,0 +1,106 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +__all__ = ["WebPubSubServiceClient"] + +from typing import TYPE_CHECKING +from copy import deepcopy + +import jwt + +import azure.core.pipeline as corepipeline +import azure.core.pipeline.policies as corepolicies +import azure.core.pipeline.transport as coretransport + +# Temporary location for types that eventually graduate to Azure Core +from .core import rest as corerest + +from ._policies import JwtCredentialPolicy + +if TYPE_CHECKING: + import azure.core.credentials as corecredentials + from azure.core.pipeline.policies import HTTPPolicy, SansIOHTTPPolicy + from typing import Any, List, cast + + +class WebPubSubServiceClient(object): + def __init__(self, endpoint, credential, **kwargs): + # type: (str, corecredentials.AzureKeyCredential, Any) -> None + """Create a new WebPubSubServiceClient instance + + :param endpoint: Endpoint to connect to. + :type endpoint: ~str + :param credential: Credentials to use to connect to endpoint. + :type credential: ~azure.core.credentials.AzureKeyCredential + :keyword api_version: Api version to use when communicating with the service. + :type api_version: str + :keyword user: User to connect as. Optional. + :type user: ~str + """ + self.endpoint = endpoint.rstrip("/") + transport = kwargs.pop("transport", None) or coretransport.RequestsTransport( + **kwargs + ) + policies = [ + corepolicies.HeadersPolicy(**kwargs), + corepolicies.UserAgentPolicy(**kwargs), + corepolicies.AsyncRetryPolicy(**kwargs), + corepolicies.ProxyPolicy(**kwargs), + corepolicies.CustomHookPolicy(**kwargs), + corepolicies.AsyncRedirectPolicy(**kwargs), + JwtCredentialPolicy(credential, kwargs.get("user", None)), + corepolicies.NetworkTraceLoggingPolicy(**kwargs), + ] # type: Any + self._pipeline = corepipeline.AsyncPipeline( + transport, + policies, + ) # type: corepipeline.AsyncPipeline + + def _format_url(self, url): + # type: (str) -> str + assert self.endpoint[-1] != "/", "My endpoint should not have a trailing slash" + return "/".join([self.endpoint, url.lstrip("/")]) + + async def send_request(self, http_request: corerest.HttpRequest, **kwargs: Any) -> corerest.AsyncHttpResponse: + """Runs the network request through the client's chained policies. + + We have helper methods to create requests specific to this service in `azure.messaging.webpubsub.rest`. + Use these helper methods to create the request you pass to this method. See our example below: + + >>> from azure.messaging.webpubsub.rest import build_healthapi_get_health_status_request + >>> request = build_healthapi_get_health_status_request(api_version) + + >>> response = await client.send_request(request) + + + For more information on this code flow, see https://aka.ms/azsdk/python/llcwiki + + For advanced cases, you can also create your own :class:`~azure.messaging.webpubsub.core.rest.HttpRequest` + and pass it in. + + :param http_request: The network request you want to make. Required. + :type http_request: ~azure.messaging.webpubsub.core.rest.HttpRequest + :keyword bool stream_response: Whether the response payload will be streamed. Defaults to False. + :return: The response of your network call. Does not do error handling on your response. + :rtype: ~azure.messaging.webpubsub.core.rest.AsyncHttpResponse + """ + request_copy = deepcopy(http_request) + request_copy.url = self._format_url(request_copy.url) + + # can't do AsyncStreamContextManager yet. This client doesn't have a pipeline client, + # AsyncStreamContextManager requires a pipeline client. WIll look more into it + # if kwargs.pop("stream_response", False): + # return corerest._AsyncStreamContextManager( + # client=self._client, + # request=request_copy, + # ) + pipeline_response = await self._pipeline.run(request_copy._internal_request, **kwargs) + return corerest.AsyncHttpResponse( + status_code=pipeline_response.http_response.status_code, + request=request_copy, + _internal_response=pipeline_response.http_response + ) From 62e514003aa898a334fb6b9285379cb4a1a37c31 Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Fri, 23 Apr 2021 12:08:30 -0400 Subject: [PATCH 17/33] Newcodegen (#4) * upgrade rest * read response before returning --- .../messaging/webpubsubservice/__init__.py | 1 + .../azure/messaging/webpubsubservice/aio.py | 4 +- .../webpubsubservice/core/rest/__init__.py | 32 +- .../webpubsubservice/core/rest/_rest.py | 1349 ++++++++--------- .../webpubsubservice/core/rest/_rest_py3.py | 51 +- 5 files changed, 664 insertions(+), 773 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index 33504145b4c4..31ae2e050ed4 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -214,4 +214,5 @@ def send_request(self, http_request, **kwargs): request=request_copy, _internal_response=pipeline_response.http_response ) + response.read() return response diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py index 7c1921139afb..a66958804a0a 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py @@ -99,8 +99,10 @@ async def send_request(self, http_request: corerest.HttpRequest, **kwargs: Any) # request=request_copy, # ) pipeline_response = await self._pipeline.run(request_copy._internal_request, **kwargs) - return corerest.AsyncHttpResponse( + response = corerest.AsyncHttpResponse( status_code=pipeline_response.http_response.status_code, request=request_copy, _internal_response=pipeline_response.http_response ) + await response.read() + return response diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py index 09575fb1cab9..a70aae7c472a 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py @@ -24,11 +24,16 @@ # # -------------------------------------------------------------------------- try: - from ._rest_py3 import HttpRequest - from ._rest_py3 import HttpResponse - from ._rest_py3 import AsyncHttpResponse - from ._rest_py3 import _StreamContextManager - from ._rest_py3 import _AsyncStreamContextManager + from ._rest_py3 import ( + HttpRequest, + HttpResponse, + AsyncHttpResponse, + _StreamContextManager, + _AsyncStreamContextManager, + StreamConsumedError, + ResponseNotReadError, + ResponseClosedError, + ) __all__ = [ "HttpRequest", @@ -36,12 +41,25 @@ "AsyncHttpResponse", "_StreamContextManager", "_AsyncStreamContextManager", + "StreamConsumedError", + "ResponseNotReadError", + "ResponseClosedError", ] except (SyntaxError, ImportError): - from ._rest import HttpRequest - from ._rest import HttpResponse + from ._rest import ( + HttpRequest, + HttpResponse, + _StreamContextManager, + StreamConsumedError, + ResponseNotReadError, + ResponseClosedError, + ) __all__ = [ "HttpRequest", "HttpResponse", + "_StreamContextManager", + "StreamConsumedError", + "ResponseNotReadError", + "ResponseClosedError", ] diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py index 89e50b9154c7..c40f6aa78195 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py @@ -1,752 +1,597 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Page not found · GitHub · GitHub - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -
- - - -
- - - - - - - - - -
-
-
-
- -
-
- 404 “This is not the web page you are looking for” - - - - - - - - - - - - -
-
- -
-
- -
- - -
-
- -
- - -
- -
- - - - - - - - - - - - - - - - - - - +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- + +from abc import abstractmethod +import sys +import six +import os +import binascii +import codecs +import cgi +import json +from enum import Enum +import xml.etree.ElementTree as ET +from typing import TYPE_CHECKING, Iterable + +from azure.core.pipeline.transport import ( + HttpRequest as _PipelineTransportHttpRequest, +) + +if TYPE_CHECKING: + from typing import ( + Any, Optional, Union, Mapping, Sequence, Tuple, Iterator + ) + ByteStream = Iterable[bytes] + + HeadersType = Union[ + Mapping[str, str], + Sequence[Tuple[str, str]] + ] + ContentType = Union[str, bytes, ByteStream] + from azure.core.pipeline.transport._base import ( + _HttpResponseBase as _PipelineTransportHttpResponseBase + ) + from azure.core._pipeline_client import PipelineClient as _PipelineClient + +class HttpVerbs(str, Enum): + GET = "GET" + PUT = "PUT" + POST = "POST" + HEAD = "HEAD" + PATCH = "PATCH" + DELETE = "DELETE" + MERGE = "MERGE" +from azure.core.exceptions import HttpResponseError + +########################### UTILS SECTION ################################# + +def _is_stream_or_str_bytes(content): + return isinstance(content, (str, bytes)) or any( + hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] + ) + +def _lookup_encoding(encoding): + # type: (str) -> bool + # including check for whether encoding is known taken from httpx + try: + codecs.lookup(encoding) + return True + except LookupError: + return False + +def _set_content_length_header(header_name, header_value, internal_request): + # type: (str, str, _PipelineTransportHttpRequest) -> None + valid_methods = ["put", "post", "patch"] + content_length_headers = ["Content-Length", "Transfer-Encoding"] + if ( + internal_request.method.lower() in valid_methods and + not any([c for c in content_length_headers if c in internal_request.headers]) + ): + internal_request.headers[header_name] = header_value + +def _set_content_type_header(header_value, internal_request): + # type: (str, _PipelineTransportHttpRequest) -> None + if not internal_request.headers.get("Content-Type"): + internal_request.headers["Content-Type"] = header_value + +def _set_content_body(content, internal_request): + # type: (ContentType, _PipelineTransportHttpRequest) -> None + headers = internal_request.headers + content_type = headers.get("Content-Type") + if _is_stream_or_str_bytes(content): + # stream will be bytes / str, or iterator of bytes / str + internal_request.set_streamed_data_body(content) + if isinstance(content, (str, bytes)) and content: + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + if isinstance(content, six.string_types): + _set_content_type_header("text/plain", internal_request) + else: + _set_content_type_header("application/octet-stream", internal_request) + elif isinstance(content, Iterable): + _set_content_length_header("Transfer-Encoding", "chunked", internal_request) + _set_content_type_header("application/octet-stream", internal_request) + elif isinstance(content, ET.Element): + # XML body + internal_request.set_xml_body(content) + _set_content_type_header("application/xml", internal_request) + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + elif content_type and content_type.startswith("text/"): + # Text body + internal_request.set_text_body(content) + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + else: + # Other body + internal_request.data = content + internal_request.headers = headers + +def _set_body(content, data, files, json_body, internal_request): + # type: (ContentType, dict, Any, Any, _PipelineTransportHttpRequest) -> None + if data is not None and not isinstance(data, dict): + content = data + data = None + if content is not None: + _set_content_body(content, internal_request) + elif json_body is not None: + internal_request.set_json_body(json_body) + _set_content_type_header("application/json", internal_request) + elif files is not None: + internal_request.set_formdata_body(files) + # if you don't supply your content type, we'll create a boundary for you with multipart/form-data + boundary = binascii.hexlify(os.urandom(16)).decode("ascii") # got logic from httpx, thanks httpx! + # _set_content_type_header("multipart/form-data; boundary={}".format(boundary), internal_request) + elif data: + _set_content_type_header("application/x-www-form-urlencoded", internal_request) + internal_request.set_formdata_body(data) + # need to set twice because Content-Type is being popped in set_formdata_body + # don't want to risk changing pipeline.transport, so doing twice here + _set_content_type_header("application/x-www-form-urlencoded", internal_request) + +def _parse_lines_from_text(text): + # largely taken from httpx's LineDecoder code + lines = [] + last_chunk_of_text = "" + while text: + text_length = len(text) + for idx in range(text_length): + curr_char = text[idx] + next_char = None if idx == len(text) - 1 else text[idx + 1] + if curr_char == "\n": + lines.append(text[: idx + 1]) + text = text[idx + 1: ] + break + if curr_char == "\r" and next_char == "\n": + # if it ends with \r\n, we only do \n + lines.append(text[:idx] + "\n") + text = text[idx + 2:] + break + if curr_char == "\r" and next_char is not None: + # if it's \r then a normal character, we switch \r to \n + lines.append(text[:idx] + "\n") + text = text[idx + 1:] + break + if next_char is None: + text = "" + last_chunk_of_text += text + break + if last_chunk_of_text.endswith("\r"): + # if ends with \r, we switch \r to \n + lines.append(last_chunk_of_text[:-1] + "\n") + elif last_chunk_of_text: + lines.append(last_chunk_of_text) + return lines + +################################## CLASSES ###################################### +class _StreamContextManager(object): + def __init__(self, client, request, **kwargs): + # type: (_PipelineClient, HttpRequest, Any) -> None + self.client = client + self.request = request + self.kwargs = kwargs + + def __enter__(self): + # type: (...) -> HttpResponse + """Actually make the call only when we enter. For sync stream_response calls""" + pipeline_transport_response = self.client._pipeline.run( + self.request._internal_request, + stream=True, + **self.kwargs + ).http_response + self.response = HttpResponse( + request=self.request, + _internal_response=pipeline_transport_response + ) + return self.response + + def __exit__(self, *args): + """Close our stream connection. For sync calls""" + self.response.__exit__(*args) + + def close(self): + self.response.close() + +class HttpRequest(object): + """Represents an HTTP request. + + :param method: HTTP method (GET, HEAD, etc.) + :type method: str or ~azure.core.protocol.HttpVerbs + :param str url: The url for your request + :keyword params: Query parameters to be mapped into your URL. Your input + should be a mapping or sequence of query name to query value(s). + :paramtype params: mapping or sequence + :keyword headers: HTTP headers you want in your request. Your input should + be a mapping or sequence of header name to header value. + :paramtype headers: mapping or sequence + :keyword dict data: Form data you want in your request body. Use for form-encoded data, i.e. + HTML forms. + :keyword any json: A JSON serializable object. We handle JSON-serialization for your + object, so use this for more complicated data structures than `data`. + :keyword files: Files you want to in your request body. Use for uploading files with + multipart encoding. Your input should be a mapping or sequence of file name to file content. + Use the `data` kwarg in addition if you want to include non-file data files as part of your request. + :paramtype files: mapping or sequence + :keyword content: Content you want in your request body. Think of it as the kwarg you should input + if your data doesn't fit into `json`, `data`, or `files`. Accepts a bytes type, or a generator + that yields bytes. + :paramtype content: str or bytes or iterable[bytes] or asynciterable[bytes] + :ivar str url: The URL this request is against. + :ivar str method: The method type of this request. + :ivar headers: The HTTP headers you passed in to your request + :vartype headers: mapping or sequence + :ivar bytes content: The content passed in for the request + """ + + def __init__(self, method, url, **kwargs): + # type: (str, str, Any) -> None + + data = kwargs.pop("data", None) + content = kwargs.pop("content", None) + json_body = kwargs.pop("json", None) + files = kwargs.pop("files", None) + + self._internal_request = kwargs.pop("_internal_request", _PipelineTransportHttpRequest( + method=method, + url=url, + headers=kwargs.pop("headers", None), + )) + params = kwargs.pop("params", None) + + if params: + self._internal_request.format_parameters(params) + + _set_body( + content=content, + data=data, + files=files, + json_body=json_body, + internal_request=self._internal_request + ) + + if kwargs: + raise TypeError( + "You have passed in kwargs '{}' that are not valid kwargs.".format( + "', '".join(list(kwargs.keys())) + ) + ) + + def _set_content_length_header(self): + method_check = self._internal_request.method.lower() in ["put", "post", "patch"] + content_length_unset = "Content-Length" not in self._internal_request.headers + if method_check and content_length_unset: + self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) + + @property + def url(self): + # type: (...) -> str + return self._internal_request.url + + @url.setter + def url(self, val): + # type: (str) -> None + self._internal_request.url = val + + @property + def method(self): + # type: (...) -> str + return self._internal_request.method + + @property + def headers(self): + # type: (...) -> HeadersType + return self._internal_request.headers + + @property + def content(self): + # type: (...) -> Any + """Gets the request content. + """ + return self._internal_request.data or self._internal_request.files + + def __repr__(self): + return self._internal_request.__repr__() + + def __deepcopy__(self, memo=None): + return HttpRequest( + self.method, + self.url, + _internal_request=self._internal_request.__deepcopy__(memo) + ) + +class _HttpResponseBase(object): + """Base class for HttpResponse and AsyncHttpResponse. + + :keyword request: The request that resulted in this response. + :paramtype request: ~azure.core.rest.HttpRequest + :ivar int status_code: The status code of this response + :ivar headers: The response headers + :vartype headers: dict[str, any] + :ivar str reason: The reason phrase for this response + :ivar bytes content: The response content in bytes + :ivar str url: The URL that resulted in this response + :ivar str encoding: The response encoding. Is settable, by default + is the response Content-Type header + :ivar str text: The response body as a string. + :ivar request: The request that resulted in this response. + :vartype request: ~azure.core.rest.HttpRequest + :ivar str content_type: The content type of the response + :ivar bool is_error: Whether this response is an error. + """ + + def __init__(self, **kwargs): + # type: (Any) -> None + self._internal_response = kwargs.pop("_internal_response") # type: _PipelineTransportHttpResponseBase + self._request = kwargs.pop("request") + self.is_closed = False + self.is_stream_consumed = False + self._num_bytes_downloaded = 0 + + @property + def status_code(self): + # type: (...) -> int + """Returns the status code of the response""" + return self._internal_response.status_code + + @status_code.setter + def status_code(self, val): + # type: (int) -> None + """Set the status code of the response""" + self._internal_response.status_code = val + + @property + def headers(self): + # type: (...) -> HeadersType + """Returns the response headers""" + return self._internal_response.headers + + @property + def reason(self): + # type: (...) -> str + """Returns the reason phrase for the response""" + return self._internal_response.reason + + @property + def content(self): + # type: (...) -> bytes + """Returns the response content in bytes""" + raise NotImplementedError() + + @property + def url(self): + # type: (...) -> str + """Returns the URL that resulted in this response""" + return self._internal_response.request.url + + @property + def encoding(self): + # type: (...) -> Optional[str] + """Returns the response encoding. By default, is specified + by the response Content-Type header. + """ + + try: + return self._encoding + except AttributeError: + return self._get_charset_encoding() + + def _get_charset_encoding(self): + content_type = self.headers.get("Content-Type") + + if not content_type: + return None + _, params = cgi.parse_header(content_type) + encoding = params.get('charset') # -> utf-8 + if encoding is None or not _lookup_encoding(encoding): + return None + return encoding + + @encoding.setter + def encoding(self, value): + # type: (str) -> None + """Sets the response encoding""" + self._encoding = value + + @property + def text(self): + # type: (...) -> str + """Returns the response body as a string""" + self.content # access content to make sure we trigger if response not fully read in + return self._internal_response.text(encoding=self.encoding) + + @property + def request(self): + # type: (...) -> HttpRequest + if self._request: + return self._request + raise RuntimeError( + "You are trying to access the 'request', but there is no request associated with this HttpResponse" + ) + + @request.setter + def request(self, val): + # type: (HttpRequest) -> None + self._request = val + + @property + def content_type(self): + # type: (...) -> Optional[str] + """Content Type of the response""" + return self._internal_response.content_type or self.headers.get("Content-Type") + + @property + def num_bytes_downloaded(self): + # type: (...) -> int + """See how many bytes of your stream response have been downloaded""" + return self._num_bytes_downloaded + + @property + def is_error(self): + # type: (...) -> bool + """See whether your HttpResponse is an error. + + Use .raise_for_status() if you want to raise if this response is an error. + """ + return self.status_code < 400 + + def json(self): + # type: (...) -> Any + """Returns the whole body as a json object. + + :return: The JSON deserialized response body + :rtype: any + :raises json.decoder.JSONDecodeError or ValueError (in python 2.7) if object is not JSON decodable: + """ + return json.loads(self.text) + + def raise_for_status(self): + # type: (...) -> None + """Raises an HttpResponseError if the response has an error status code. + + If response is good, does nothing. + """ + if self.status_code >= 400: + raise HttpResponseError(response=self) + + def __repr__(self): + # type: (...) -> str + content_type_str = ( + ", Content-Type: {}".format(self.content_type) if self.content_type else "" + ) + return "<{}: {} {}{}>".format( + type(self).__name__, self.status_code, self.reason, content_type_str + ) + + def _validate_streaming_access(self): + # type: (...) -> None + if self.is_closed: + raise ResponseClosedError() + if self.is_stream_consumed: + raise StreamConsumedError() + +class HttpResponse(_HttpResponseBase): + + @property + def content(self): + # type: (...) -> bytes + try: + return self._content + except AttributeError: + raise ResponseNotReadError() + + def close(self): + # type: (...) -> None + self.is_closed = True + self._internal_response.internal_response.close() + + def __exit__(self, *args): + # type: (...) -> None + self._internal_response.internal_response.__exit__(*args) + + def read(self): + # type: (...) -> bytes + """ + Read the response's bytes. + + """ + try: + return self._content + except AttributeError: + self._validate_streaming_access() + self._content = ( + self._internal_response.body() or + b"".join(self.iter_raw()) + ) + self._close_stream() + return self._content + + def iter_bytes(self, chunk_size=None): + # type: (int) -> Iterator[bytes] + """Iterate over the bytes in the response stream + """ + try: + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), chunk_size): + yield self._content[i: i + chunk_size] + + except AttributeError: + for raw_bytes in self.iter_raw(chunk_size=chunk_size): + yield raw_bytes + + def iter_text(self, chunk_size=None): + # type: (int) -> Iterator[str] + """Iterate over the response text + """ + for byte in self.iter_bytes(chunk_size): + text = byte.decode(self.encoding or "utf-8") + yield text + + def iter_lines(self, chunk_size=None): + # type: (int) -> Iterator[str] + for text in self.iter_text(chunk_size): + lines = _parse_lines_from_text(text) + for line in lines: + yield line + + def _close_stream(self): + # type: (...) -> None + self.is_stream_consumed = True + self.close() + + def iter_raw(self, chunk_size=None): + # type: (int) -> Iterator[bytes] + """Iterate over the raw response bytes + """ + self._validate_streaming_access() + stream_download = self._internal_response.stream_download(None) + for raw_bytes in stream_download: + self._num_bytes_downloaded += len(raw_bytes) + yield raw_bytes + + self._close_stream() + +########################### ERRORS SECTION ################################# + +class StreamConsumedError(Exception): + def __init__(self): + message = ( + "You are attempting to read or stream content that has already been streamed. " + "You have likely already consumed this stream, so it can not be accessed anymore." + ) + super(StreamConsumedError, self).__init__(message) + +class ResponseClosedError(Exception): + def __init__(self): + message = ( + "You can not try to read or stream this response's content, since the " + "response has been closed." + ) + super(ResponseClosedError, self).__init__(message) + +class ResponseNotReadError(Exception): + + def __init__(self): + message = ( + "You have not read in the response's bytes yet. Call response.read() first." + ) + super(ResponseNotReadError, self).__init__(message) \ No newline at end of file diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py index fd86cbf0711b..2725aed72221 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py @@ -23,11 +23,6 @@ # IN THE SOFTWARE. # # -------------------------------------------------------------------------- - -__all__ = [ - "HttpRequest", - "HttpResponse", -] import asyncio import os import binascii @@ -50,6 +45,7 @@ List, ) from abc import abstractmethod +from azure.core.exceptions import HttpResponseError ################################### TYPES SECTION ######################### @@ -491,6 +487,7 @@ def encoding(self, value: str) -> None: @property def text(self) -> str: """Returns the response body as a string""" + self.content # access content to make sure we trigger if response not fully read in return self._internal_response.text(encoding=self.encoding) @property @@ -529,7 +526,8 @@ def raise_for_status(self) -> None: If response is good, does nothing. """ - return self._internal_response.raise_for_status() + if self.status_code >= 400: + raise HttpResponseError(response=self) def __repr__(self) -> str: content_type_str = ( @@ -541,9 +539,9 @@ def __repr__(self) -> str: def _validate_streaming_access(self) -> None: if self.is_closed: - raise TypeError("Can not iterate over stream, it is closed.") + raise ResponseClosedError() if self.is_stream_consumed: - raise TypeError("Can not iterate over stream, it has been fully consumed") + raise StreamConsumedError() class HttpResponse(_HttpResponseBase): @@ -553,7 +551,7 @@ def content(self): try: return self._content except AttributeError: - raise TypeError("You have not read in the response's bytes yet. Call response.read() first.") + raise ResponseNotReadError() def close(self) -> None: self.is_closed = True @@ -611,7 +609,7 @@ def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: """Iterate over the raw response bytes """ self._validate_streaming_access() - stream_download = self._internal_response.stream_download(None, chunk_size=chunk_size) + stream_download = self._internal_response.stream_download(None) for raw_bytes in stream_download: self._num_bytes_downloaded += len(raw_bytes) yield raw_bytes @@ -626,7 +624,7 @@ def content(self) -> bytes: try: return self._content except AttributeError: - raise TypeError("You have not read in the response's bytes yet. Call response.read() first.") + raise ResponseNotReadError() async def _close_stream(self) -> None: self.is_stream_consumed = True @@ -675,7 +673,7 @@ async def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: """Iterate over the raw response bytes """ self._validate_streaming_access() - stream_download = self._internal_response.stream_download(None, chunk_size=chunk_size) + stream_download = self._internal_response.stream_download(None) async for raw_bytes in stream_download: self._num_bytes_downloaded += len(raw_bytes) yield raw_bytes @@ -688,4 +686,31 @@ async def close(self) -> None: await asyncio.sleep(0) async def __aexit__(self, *args) -> None: - await self._internal_response.internal_response.__aexit__(*args) \ No newline at end of file + await self._internal_response.internal_response.__aexit__(*args) + + +########################### ERRORS SECTION ################################# + +class StreamConsumedError(Exception): + def __init__(self) -> None: + message = ( + "You are attempting to read or stream content that has already been streamed. " + "You have likely already consumed this stream, so it can not be accessed anymore." + ) + super().__init__(message) + +class ResponseClosedError(Exception): + def __init__(self) -> None: + message = ( + "You can not try to read or stream this response's content, since the " + "response has been closed." + ) + super().__init__(message) + +class ResponseNotReadError(Exception): + + def __init__(self) -> None: + message = ( + "You have not read in the response's bytes yet. Call response.read() first." + ) + super().__init__(message) From c745cdbb27cb6ac2201a056649d02cf39105a058 Mon Sep 17 00:00:00 2001 From: "Johan Stenberg (MSFT)" Date: Fri, 23 Apr 2021 14:51:23 -0700 Subject: [PATCH 18/33] Updated to newer codegen for web pubsub (#3) * Update to later codegen * Added async client --- .../messaging/webpubsubservice/__init__.py | 68 +- .../azure/messaging/webpubsubservice/aio.py | 108 +++ .../messaging/webpubsubservice/core/rest.py | 275 ------- .../webpubsubservice/core/rest/__init__.py | 65 ++ .../webpubsubservice/core/rest/_rest.py | 597 +++++++++++++++ .../webpubsubservice/core/rest/_rest_py3.py | 716 ++++++++++++++++++ .../azure/messaging/webpubsubservice/rest.py | 305 ++++---- .../examples/send_messages.py | 18 +- 8 files changed, 1686 insertions(+), 466 deletions(-) create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py delete mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py create mode 100644 sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index f069edba3aed..31ae2e050ed4 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -11,6 +11,7 @@ __all__ = ["build_authentication_token", "WebPubSubServiceClient"] +from copy import deepcopy from datetime import datetime, timedelta from typing import TYPE_CHECKING @@ -102,7 +103,7 @@ def __init__(self, endpoint, credential, **kwargs): :param endpoint: Endpoint to connect to. :type endpoint: ~str :param credential: Credentials to use to connect to endpoint. - :type credential: ~azure.core.credentials.AzureKeyCredentials + :type credential: ~azure.core.credentials.AzureKeyCredential :keyword api_version: Api version to use when communicating with the service. :type api_version: str :keyword user: User to connect as. Optional. @@ -121,6 +122,7 @@ def __init__(self, endpoint, credential, **kwargs): corepolicies.CustomHookPolicy(**kwargs), corepolicies.RedirectPolicy(**kwargs), JwtCredentialPolicy(credential, kwargs.get("user", None)), + corepolicies.ContentDecodePolicy(**kwargs), corepolicies.NetworkTraceLoggingPolicy(**kwargs), ] # type: Any self._pipeline = corepipeline.Pipeline( @@ -132,6 +134,12 @@ def __init__(self, endpoint, credential, **kwargs): @classmethod def from_connection_string(cls, connection_string, **kwargs): # type: (Type[ClientType], str, Any) -> ClientType + """Create a new WebPubSubServiceClient from a connection string. + + :param connection_string: Connection string + :type connection_string: ~str + :rtype: WebPubSubServiceClient + """ for invalid_keyword_arg in ('endpoint', 'accesskey'): if invalid_keyword_arg in kwargs: raise TypeError('Unknown argument {}'.format(invalid_keyword_arg)) @@ -145,8 +153,9 @@ def from_connection_string(cls, connection_string, **kwargs): # Let's map it to whatthe constructor actually wants... key = 'api_version' kwargs[key] = value - else: - raise ValueError("Malformed connection string - expected 'key=value', got {}".format(segment)) + elif segment: + raise ValueError("Malformed connection string - expected 'key=value', found segment '{}' in '{}'" + .format(segment, connection_string)) if 'endpoint' not in kwargs: raise ValueError("connection_string missing 'endpoint' field") @@ -165,24 +174,45 @@ def _format_url(self, url): assert self.endpoint[-1] != "/", "My endpoint should not have a trailing slash" return "/".join([self.endpoint, url.lstrip("/")]) - def send_request(self, request, **kwargs): + def send_request(self, http_request, **kwargs): # type: (corerest.HttpRequest, Any) -> corerest.HttpResponse """Runs the network request through the client's chained policies. - :param request: The network request you want to make. Required. - :type request: ~corerest.HttpRequest - :keyword bool stream: Whether the response payload will be streamed. Defaults to False - :return: The response of your network call. - :rtype: ~corerest.HttpResponse + We have helper methods to create requests specific to this service in `azure.messaging.webpubsub.rest`. + Use these helper methods to create the request you pass to this method. See our example below: + + >>> from azure.messaging.webpubsub.rest import build_healthapi_get_health_status_request + >>> request = build_healthapi_get_health_status_request(api_version) + + >>> response = client.send_request(request) + + + For more information on this code flow, see https://aka.ms/azsdk/python/llcwiki + + For advanced cases, you can also create your own :class:`~azure.messaging.webpubsub.core.rest.HttpRequest` + and pass it in. + + :param http_request: The network request you want to make. Required. + :type http_request: ~azure.messaging.webpubsub.core.rest.HttpRequest + :keyword bool stream_response: Whether the response payload will be streamed. Defaults to False. + :return: The response of your network call. Does not do error handling on your response. + :rtype: ~azure.messaging.webpubsub.core.rest.HttpResponse """ - kwargs.setdefault("stream", False) - request.url = self._format_url( - request.url - ) # BUGBUG - should create new request, not mutate the existing one... - pipeline_response = self._pipeline.run( - request._internal_request, **kwargs # pylint: disable=W0212 - ) - return corerest.HttpResponse( - request=request, - _internal_response=pipeline_response.http_response, + request_copy = deepcopy(http_request) + request_copy.url = self._format_url(request_copy.url) + + # can't do StreamCOntextManager yet. This client doesn't have a pipeline client, + # StreamContextManager requires a pipeline client. WIll look more into it + # if kwargs.pop("stream_response", False): + # return corerest._StreamContextManager( + # client=self._client, + # request=request_copy, + # ) + pipeline_response = self._pipeline.run(request_copy._internal_request, **kwargs) + response = corerest.HttpResponse( + status_code=pipeline_response.http_response.status_code, + request=request_copy, + _internal_response=pipeline_response.http_response ) + response.read() + return response diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py new file mode 100644 index 000000000000..a66958804a0a --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py @@ -0,0 +1,108 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +__all__ = ["WebPubSubServiceClient"] + +from typing import TYPE_CHECKING +from copy import deepcopy + +import jwt + +import azure.core.pipeline as corepipeline +import azure.core.pipeline.policies as corepolicies +import azure.core.pipeline.transport as coretransport + +# Temporary location for types that eventually graduate to Azure Core +from .core import rest as corerest + +from ._policies import JwtCredentialPolicy + +if TYPE_CHECKING: + import azure.core.credentials as corecredentials + from azure.core.pipeline.policies import HTTPPolicy, SansIOHTTPPolicy + from typing import Any, List, cast + + +class WebPubSubServiceClient(object): + def __init__(self, endpoint, credential, **kwargs): + # type: (str, corecredentials.AzureKeyCredential, Any) -> None + """Create a new WebPubSubServiceClient instance + + :param endpoint: Endpoint to connect to. + :type endpoint: ~str + :param credential: Credentials to use to connect to endpoint. + :type credential: ~azure.core.credentials.AzureKeyCredential + :keyword api_version: Api version to use when communicating with the service. + :type api_version: str + :keyword user: User to connect as. Optional. + :type user: ~str + """ + self.endpoint = endpoint.rstrip("/") + transport = kwargs.pop("transport", None) or coretransport.RequestsTransport( + **kwargs + ) + policies = [ + corepolicies.HeadersPolicy(**kwargs), + corepolicies.UserAgentPolicy(**kwargs), + corepolicies.AsyncRetryPolicy(**kwargs), + corepolicies.ProxyPolicy(**kwargs), + corepolicies.CustomHookPolicy(**kwargs), + corepolicies.AsyncRedirectPolicy(**kwargs), + JwtCredentialPolicy(credential, kwargs.get("user", None)), + corepolicies.NetworkTraceLoggingPolicy(**kwargs), + ] # type: Any + self._pipeline = corepipeline.AsyncPipeline( + transport, + policies, + ) # type: corepipeline.AsyncPipeline + + def _format_url(self, url): + # type: (str) -> str + assert self.endpoint[-1] != "/", "My endpoint should not have a trailing slash" + return "/".join([self.endpoint, url.lstrip("/")]) + + async def send_request(self, http_request: corerest.HttpRequest, **kwargs: Any) -> corerest.AsyncHttpResponse: + """Runs the network request through the client's chained policies. + + We have helper methods to create requests specific to this service in `azure.messaging.webpubsub.rest`. + Use these helper methods to create the request you pass to this method. See our example below: + + >>> from azure.messaging.webpubsub.rest import build_healthapi_get_health_status_request + >>> request = build_healthapi_get_health_status_request(api_version) + + >>> response = await client.send_request(request) + + + For more information on this code flow, see https://aka.ms/azsdk/python/llcwiki + + For advanced cases, you can also create your own :class:`~azure.messaging.webpubsub.core.rest.HttpRequest` + and pass it in. + + :param http_request: The network request you want to make. Required. + :type http_request: ~azure.messaging.webpubsub.core.rest.HttpRequest + :keyword bool stream_response: Whether the response payload will be streamed. Defaults to False. + :return: The response of your network call. Does not do error handling on your response. + :rtype: ~azure.messaging.webpubsub.core.rest.AsyncHttpResponse + """ + request_copy = deepcopy(http_request) + request_copy.url = self._format_url(request_copy.url) + + # can't do AsyncStreamContextManager yet. This client doesn't have a pipeline client, + # AsyncStreamContextManager requires a pipeline client. WIll look more into it + # if kwargs.pop("stream_response", False): + # return corerest._AsyncStreamContextManager( + # client=self._client, + # request=request_copy, + # ) + pipeline_response = await self._pipeline.run(request_copy._internal_request, **kwargs) + response = corerest.AsyncHttpResponse( + status_code=pipeline_response.http_response.status_code, + request=request_copy, + _internal_response=pipeline_response.http_response + ) + await response.read() + return response diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py deleted file mode 100644 index 5d5b2ff09956..000000000000 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest.py +++ /dev/null @@ -1,275 +0,0 @@ -# -------------------------------------------------------------------------- -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# The MIT License (MIT) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the ""Software""), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -# IN THE SOFTWARE. -# -# -------------------------------------------------------------------------- - -__all__ = ["HttpRequest", "HttpResponse"] - -import json as jsonlib -import xml.etree.ElementTree as ET -from typing import TYPE_CHECKING - -from azure.core.pipeline.transport import ( - HttpRequest as _PipelineTransportHttpRequest, - HttpResponse as _PipelineTransportHttpResponse, -) - -if TYPE_CHECKING: - from typing import Any, Optional, Union, Mapping, Sequence, Tuple - - HeaderTypes = Union[Mapping[str, str], Sequence[Tuple[str, str]]] - - -def _is_stream(content): - return isinstance(content, (str, bytes)) or any( - hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] - ) - - -class HttpRequest(object): - """Represents an HTTP request. - - :param method: HTTP method (GET, HEAD, etc.) - :type method: str or ~azure.core.protocol.HttpVerbs - :param str url: The url for your request - :keyword params: Query parameters to be mapped into your URL. Your input - should be a mapping or sequence of query name to query value(s). - :paramtype params: mapping or sequence - :keyword headers: HTTP headers you want in your request. Your input should - be a mapping or sequence of header name to header value. - :paramtype headers: mapping or sequence - :keyword dict data: Form data you want in your request body. Use for form-encoded data, i.e. - HTML forms. - :keyword any json: A JSON serializable object. We handle JSON-serialization for your - object, so use this for more complicated data structures than `data`. - :keyword files: Files you want to in your request body. Use for uploading files with - multipart encoding. Your input should be a mapping or sequence of file name to file content. - Use the `data` kwarg in addition if you want to include non-file data files as part of your request. - :paramtype files: mapping or sequence - :keyword content: Content you want in your request body. Think of it as the kwarg you should input - if your data doesn't fit into `json`, `data`, or `files`. Accepts a bytes type, or a generator - that yields bytes. - :paramtype content: str or bytes or iterable[bytes] or asynciterable[bytes] - """ - - def __init__(self, method, url, **kwargs): - # type: (str, str, Any) -> None - - data = kwargs.pop("data", None) - content = kwargs.pop("content", None) - json = kwargs.pop("json", None) - files = kwargs.pop("files", None) - - self._internal_request = _PipelineTransportHttpRequest( - method=method, - url=url, - headers=kwargs.pop("headers", None), - ) - params = kwargs.pop("params", None) - - if params: - self._internal_request.format_parameters(params) - if data is not None: - self._internal_request.set_formdata_body(data) - if content is not None: - content_type = self._internal_request.headers.get("Content-Type") - if _is_stream(content): - self._internal_request.set_streamed_data_body(content) - elif isinstance(content, ET.Element): - self._internal_request.set_xml_body(content) - elif content_type and content_type.startswith("text/"): - self._internal_request.set_text_body(content) - else: - self._internal_request.data = content - if json is not None: - self._internal_request.set_json_body(json) - if not self._internal_request.headers.get("Content-Type"): - self._internal_request.headers["Content-Type"] = "application/json" - if files is not None: - self._internal_request.set_formdata_body(files) - - if not self._internal_request.headers.get("Content-Length"): - try: - # set content length header if possible - self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) # type: ignore - except TypeError: - pass - self.method = self._internal_request.method - if kwargs: - raise TypeError( - "You have passed in kwargs '{}' that are not valid kwargs.".format( - "', '".join(list(kwargs.keys())) - ) - ) - - @property - def url(self): - # type: (...) -> str - return self._internal_request.url - - @url.setter - def url(self, val): - # type: (str) -> None - self._internal_request.url = val - - @property - def method(self): - # type: (...) -> str - return self._internal_request.method - - @method.setter - def method(self, val): - # type: (str) -> None - self._internal_request.method = val - - @property - def headers(self): - return self._internal_request.headers - - @property - def content(self): - return self._internal_request.data - - def __repr__(self): - return "".format(self.method, self.url) - - def __deepcopy__(self, memo=None): - return self._internal_request.__deepcopy__(memo) - - -class _HttpResponseBase(object): - """Base class for HttpResponse and AsyncHttpResponse. - :param int status_code: Status code of the response. - :keyword headers: Response headers - :paramtype headers: dict[str, any] - :keyword str text: The response content as a string - :keyword any json: JSON content - :keyword stream: Streamed response - :paramtype stream: bytes or iterator of bytes - :keyword callable on_close: Any callable you want to cal - when closing your HttpResponse - :keyword history: If redirection, history of all redirection - that resulted in this response. - :paramtype history: list[~azure.core.protocol.HttpResponse] - """ - - def __init__(self, **kwargs): - # type: (Any) -> None - self._internal_response = kwargs.pop( - "_internal_response" - ) # type: _PipelineTransportHttpResponse - self.request = kwargs.pop("request") - self._encoding = "" - - @property - def status_code(self): - # type: (...) -> int - """Returns the status code of the response""" - return self._internal_response.status_code or -1 - - @status_code.setter - def status_code(self, val): - # type: (int) -> None - """Set the status code of the response""" - self._internal_response.status_code = val - - @property - def headers(self): - # type: (...) -> HeaderTypes - """Returns the response headers""" - return self._internal_response.headers - - @property - def reason(self): - # type: (...) -> str - """Returns the reason phrase for the response""" - return self._internal_response.reason or "" - - @property - def content(self): - # type: (...) -> bytes - """Returns the response content in bytes""" - raise NotImplementedError() - - @property - def url(self): - # type: (...) -> str - """Returns the URL that resulted in this response""" - return self._internal_response.request.url - - @property - def encoding(self): - # type: (...) -> Optional[str] - """Returns the response encoding. By default, is specified - by the response Content-Type header. - """ - return self._encoding - - @encoding.setter - def encoding(self, value): - # type: (str) -> None - """Sets the response encoding""" - self._encoding = value - - @property - def text(self): - # type: (...) -> str - """Returns the response body as a string""" - return self._internal_response.text(self.encoding) - - def json(self): - # type: (...) -> Any - """Returns the whole body as a json object. - - :return: The JSON deserialized response body - :rtype: any - :raises json.decoder.JSONDecodeError or ValueError (in python 2.7) if object is not JSON decodable: - """ - return jsonlib.loads(self._internal_response.text(self.encoding)) - - def raise_for_status(self): - # type: () -> None - """Raises an HttpResponseError if the response has an error status code. - - If response is good, does nothing. - """ - self._internal_response.raise_for_status() - - def __repr__(self): - # type: (...) -> str - return repr(self._internal_response) - - -class HttpResponse(_HttpResponseBase): - @property - def content(self): - # type: (...) -> bytes - return self._internal_response.body() - - -class AsyncHttpResponse(_HttpResponseBase): - @property - def content(self): - # type: (...) -> bytes - return self._internal_response.body() diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py new file mode 100644 index 000000000000..a70aae7c472a --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/__init__.py @@ -0,0 +1,65 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- +try: + from ._rest_py3 import ( + HttpRequest, + HttpResponse, + AsyncHttpResponse, + _StreamContextManager, + _AsyncStreamContextManager, + StreamConsumedError, + ResponseNotReadError, + ResponseClosedError, + ) + + __all__ = [ + "HttpRequest", + "HttpResponse", + "AsyncHttpResponse", + "_StreamContextManager", + "_AsyncStreamContextManager", + "StreamConsumedError", + "ResponseNotReadError", + "ResponseClosedError", + ] +except (SyntaxError, ImportError): + from ._rest import ( + HttpRequest, + HttpResponse, + _StreamContextManager, + StreamConsumedError, + ResponseNotReadError, + ResponseClosedError, + ) + + __all__ = [ + "HttpRequest", + "HttpResponse", + "_StreamContextManager", + "StreamConsumedError", + "ResponseNotReadError", + "ResponseClosedError", + ] diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py new file mode 100644 index 000000000000..c40f6aa78195 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py @@ -0,0 +1,597 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- + +from abc import abstractmethod +import sys +import six +import os +import binascii +import codecs +import cgi +import json +from enum import Enum +import xml.etree.ElementTree as ET +from typing import TYPE_CHECKING, Iterable + +from azure.core.pipeline.transport import ( + HttpRequest as _PipelineTransportHttpRequest, +) + +if TYPE_CHECKING: + from typing import ( + Any, Optional, Union, Mapping, Sequence, Tuple, Iterator + ) + ByteStream = Iterable[bytes] + + HeadersType = Union[ + Mapping[str, str], + Sequence[Tuple[str, str]] + ] + ContentType = Union[str, bytes, ByteStream] + from azure.core.pipeline.transport._base import ( + _HttpResponseBase as _PipelineTransportHttpResponseBase + ) + from azure.core._pipeline_client import PipelineClient as _PipelineClient + +class HttpVerbs(str, Enum): + GET = "GET" + PUT = "PUT" + POST = "POST" + HEAD = "HEAD" + PATCH = "PATCH" + DELETE = "DELETE" + MERGE = "MERGE" +from azure.core.exceptions import HttpResponseError + +########################### UTILS SECTION ################################# + +def _is_stream_or_str_bytes(content): + return isinstance(content, (str, bytes)) or any( + hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] + ) + +def _lookup_encoding(encoding): + # type: (str) -> bool + # including check for whether encoding is known taken from httpx + try: + codecs.lookup(encoding) + return True + except LookupError: + return False + +def _set_content_length_header(header_name, header_value, internal_request): + # type: (str, str, _PipelineTransportHttpRequest) -> None + valid_methods = ["put", "post", "patch"] + content_length_headers = ["Content-Length", "Transfer-Encoding"] + if ( + internal_request.method.lower() in valid_methods and + not any([c for c in content_length_headers if c in internal_request.headers]) + ): + internal_request.headers[header_name] = header_value + +def _set_content_type_header(header_value, internal_request): + # type: (str, _PipelineTransportHttpRequest) -> None + if not internal_request.headers.get("Content-Type"): + internal_request.headers["Content-Type"] = header_value + +def _set_content_body(content, internal_request): + # type: (ContentType, _PipelineTransportHttpRequest) -> None + headers = internal_request.headers + content_type = headers.get("Content-Type") + if _is_stream_or_str_bytes(content): + # stream will be bytes / str, or iterator of bytes / str + internal_request.set_streamed_data_body(content) + if isinstance(content, (str, bytes)) and content: + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + if isinstance(content, six.string_types): + _set_content_type_header("text/plain", internal_request) + else: + _set_content_type_header("application/octet-stream", internal_request) + elif isinstance(content, Iterable): + _set_content_length_header("Transfer-Encoding", "chunked", internal_request) + _set_content_type_header("application/octet-stream", internal_request) + elif isinstance(content, ET.Element): + # XML body + internal_request.set_xml_body(content) + _set_content_type_header("application/xml", internal_request) + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + elif content_type and content_type.startswith("text/"): + # Text body + internal_request.set_text_body(content) + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + else: + # Other body + internal_request.data = content + internal_request.headers = headers + +def _set_body(content, data, files, json_body, internal_request): + # type: (ContentType, dict, Any, Any, _PipelineTransportHttpRequest) -> None + if data is not None and not isinstance(data, dict): + content = data + data = None + if content is not None: + _set_content_body(content, internal_request) + elif json_body is not None: + internal_request.set_json_body(json_body) + _set_content_type_header("application/json", internal_request) + elif files is not None: + internal_request.set_formdata_body(files) + # if you don't supply your content type, we'll create a boundary for you with multipart/form-data + boundary = binascii.hexlify(os.urandom(16)).decode("ascii") # got logic from httpx, thanks httpx! + # _set_content_type_header("multipart/form-data; boundary={}".format(boundary), internal_request) + elif data: + _set_content_type_header("application/x-www-form-urlencoded", internal_request) + internal_request.set_formdata_body(data) + # need to set twice because Content-Type is being popped in set_formdata_body + # don't want to risk changing pipeline.transport, so doing twice here + _set_content_type_header("application/x-www-form-urlencoded", internal_request) + +def _parse_lines_from_text(text): + # largely taken from httpx's LineDecoder code + lines = [] + last_chunk_of_text = "" + while text: + text_length = len(text) + for idx in range(text_length): + curr_char = text[idx] + next_char = None if idx == len(text) - 1 else text[idx + 1] + if curr_char == "\n": + lines.append(text[: idx + 1]) + text = text[idx + 1: ] + break + if curr_char == "\r" and next_char == "\n": + # if it ends with \r\n, we only do \n + lines.append(text[:idx] + "\n") + text = text[idx + 2:] + break + if curr_char == "\r" and next_char is not None: + # if it's \r then a normal character, we switch \r to \n + lines.append(text[:idx] + "\n") + text = text[idx + 1:] + break + if next_char is None: + text = "" + last_chunk_of_text += text + break + if last_chunk_of_text.endswith("\r"): + # if ends with \r, we switch \r to \n + lines.append(last_chunk_of_text[:-1] + "\n") + elif last_chunk_of_text: + lines.append(last_chunk_of_text) + return lines + +################################## CLASSES ###################################### +class _StreamContextManager(object): + def __init__(self, client, request, **kwargs): + # type: (_PipelineClient, HttpRequest, Any) -> None + self.client = client + self.request = request + self.kwargs = kwargs + + def __enter__(self): + # type: (...) -> HttpResponse + """Actually make the call only when we enter. For sync stream_response calls""" + pipeline_transport_response = self.client._pipeline.run( + self.request._internal_request, + stream=True, + **self.kwargs + ).http_response + self.response = HttpResponse( + request=self.request, + _internal_response=pipeline_transport_response + ) + return self.response + + def __exit__(self, *args): + """Close our stream connection. For sync calls""" + self.response.__exit__(*args) + + def close(self): + self.response.close() + +class HttpRequest(object): + """Represents an HTTP request. + + :param method: HTTP method (GET, HEAD, etc.) + :type method: str or ~azure.core.protocol.HttpVerbs + :param str url: The url for your request + :keyword params: Query parameters to be mapped into your URL. Your input + should be a mapping or sequence of query name to query value(s). + :paramtype params: mapping or sequence + :keyword headers: HTTP headers you want in your request. Your input should + be a mapping or sequence of header name to header value. + :paramtype headers: mapping or sequence + :keyword dict data: Form data you want in your request body. Use for form-encoded data, i.e. + HTML forms. + :keyword any json: A JSON serializable object. We handle JSON-serialization for your + object, so use this for more complicated data structures than `data`. + :keyword files: Files you want to in your request body. Use for uploading files with + multipart encoding. Your input should be a mapping or sequence of file name to file content. + Use the `data` kwarg in addition if you want to include non-file data files as part of your request. + :paramtype files: mapping or sequence + :keyword content: Content you want in your request body. Think of it as the kwarg you should input + if your data doesn't fit into `json`, `data`, or `files`. Accepts a bytes type, or a generator + that yields bytes. + :paramtype content: str or bytes or iterable[bytes] or asynciterable[bytes] + :ivar str url: The URL this request is against. + :ivar str method: The method type of this request. + :ivar headers: The HTTP headers you passed in to your request + :vartype headers: mapping or sequence + :ivar bytes content: The content passed in for the request + """ + + def __init__(self, method, url, **kwargs): + # type: (str, str, Any) -> None + + data = kwargs.pop("data", None) + content = kwargs.pop("content", None) + json_body = kwargs.pop("json", None) + files = kwargs.pop("files", None) + + self._internal_request = kwargs.pop("_internal_request", _PipelineTransportHttpRequest( + method=method, + url=url, + headers=kwargs.pop("headers", None), + )) + params = kwargs.pop("params", None) + + if params: + self._internal_request.format_parameters(params) + + _set_body( + content=content, + data=data, + files=files, + json_body=json_body, + internal_request=self._internal_request + ) + + if kwargs: + raise TypeError( + "You have passed in kwargs '{}' that are not valid kwargs.".format( + "', '".join(list(kwargs.keys())) + ) + ) + + def _set_content_length_header(self): + method_check = self._internal_request.method.lower() in ["put", "post", "patch"] + content_length_unset = "Content-Length" not in self._internal_request.headers + if method_check and content_length_unset: + self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) + + @property + def url(self): + # type: (...) -> str + return self._internal_request.url + + @url.setter + def url(self, val): + # type: (str) -> None + self._internal_request.url = val + + @property + def method(self): + # type: (...) -> str + return self._internal_request.method + + @property + def headers(self): + # type: (...) -> HeadersType + return self._internal_request.headers + + @property + def content(self): + # type: (...) -> Any + """Gets the request content. + """ + return self._internal_request.data or self._internal_request.files + + def __repr__(self): + return self._internal_request.__repr__() + + def __deepcopy__(self, memo=None): + return HttpRequest( + self.method, + self.url, + _internal_request=self._internal_request.__deepcopy__(memo) + ) + +class _HttpResponseBase(object): + """Base class for HttpResponse and AsyncHttpResponse. + + :keyword request: The request that resulted in this response. + :paramtype request: ~azure.core.rest.HttpRequest + :ivar int status_code: The status code of this response + :ivar headers: The response headers + :vartype headers: dict[str, any] + :ivar str reason: The reason phrase for this response + :ivar bytes content: The response content in bytes + :ivar str url: The URL that resulted in this response + :ivar str encoding: The response encoding. Is settable, by default + is the response Content-Type header + :ivar str text: The response body as a string. + :ivar request: The request that resulted in this response. + :vartype request: ~azure.core.rest.HttpRequest + :ivar str content_type: The content type of the response + :ivar bool is_error: Whether this response is an error. + """ + + def __init__(self, **kwargs): + # type: (Any) -> None + self._internal_response = kwargs.pop("_internal_response") # type: _PipelineTransportHttpResponseBase + self._request = kwargs.pop("request") + self.is_closed = False + self.is_stream_consumed = False + self._num_bytes_downloaded = 0 + + @property + def status_code(self): + # type: (...) -> int + """Returns the status code of the response""" + return self._internal_response.status_code + + @status_code.setter + def status_code(self, val): + # type: (int) -> None + """Set the status code of the response""" + self._internal_response.status_code = val + + @property + def headers(self): + # type: (...) -> HeadersType + """Returns the response headers""" + return self._internal_response.headers + + @property + def reason(self): + # type: (...) -> str + """Returns the reason phrase for the response""" + return self._internal_response.reason + + @property + def content(self): + # type: (...) -> bytes + """Returns the response content in bytes""" + raise NotImplementedError() + + @property + def url(self): + # type: (...) -> str + """Returns the URL that resulted in this response""" + return self._internal_response.request.url + + @property + def encoding(self): + # type: (...) -> Optional[str] + """Returns the response encoding. By default, is specified + by the response Content-Type header. + """ + + try: + return self._encoding + except AttributeError: + return self._get_charset_encoding() + + def _get_charset_encoding(self): + content_type = self.headers.get("Content-Type") + + if not content_type: + return None + _, params = cgi.parse_header(content_type) + encoding = params.get('charset') # -> utf-8 + if encoding is None or not _lookup_encoding(encoding): + return None + return encoding + + @encoding.setter + def encoding(self, value): + # type: (str) -> None + """Sets the response encoding""" + self._encoding = value + + @property + def text(self): + # type: (...) -> str + """Returns the response body as a string""" + self.content # access content to make sure we trigger if response not fully read in + return self._internal_response.text(encoding=self.encoding) + + @property + def request(self): + # type: (...) -> HttpRequest + if self._request: + return self._request + raise RuntimeError( + "You are trying to access the 'request', but there is no request associated with this HttpResponse" + ) + + @request.setter + def request(self, val): + # type: (HttpRequest) -> None + self._request = val + + @property + def content_type(self): + # type: (...) -> Optional[str] + """Content Type of the response""" + return self._internal_response.content_type or self.headers.get("Content-Type") + + @property + def num_bytes_downloaded(self): + # type: (...) -> int + """See how many bytes of your stream response have been downloaded""" + return self._num_bytes_downloaded + + @property + def is_error(self): + # type: (...) -> bool + """See whether your HttpResponse is an error. + + Use .raise_for_status() if you want to raise if this response is an error. + """ + return self.status_code < 400 + + def json(self): + # type: (...) -> Any + """Returns the whole body as a json object. + + :return: The JSON deserialized response body + :rtype: any + :raises json.decoder.JSONDecodeError or ValueError (in python 2.7) if object is not JSON decodable: + """ + return json.loads(self.text) + + def raise_for_status(self): + # type: (...) -> None + """Raises an HttpResponseError if the response has an error status code. + + If response is good, does nothing. + """ + if self.status_code >= 400: + raise HttpResponseError(response=self) + + def __repr__(self): + # type: (...) -> str + content_type_str = ( + ", Content-Type: {}".format(self.content_type) if self.content_type else "" + ) + return "<{}: {} {}{}>".format( + type(self).__name__, self.status_code, self.reason, content_type_str + ) + + def _validate_streaming_access(self): + # type: (...) -> None + if self.is_closed: + raise ResponseClosedError() + if self.is_stream_consumed: + raise StreamConsumedError() + +class HttpResponse(_HttpResponseBase): + + @property + def content(self): + # type: (...) -> bytes + try: + return self._content + except AttributeError: + raise ResponseNotReadError() + + def close(self): + # type: (...) -> None + self.is_closed = True + self._internal_response.internal_response.close() + + def __exit__(self, *args): + # type: (...) -> None + self._internal_response.internal_response.__exit__(*args) + + def read(self): + # type: (...) -> bytes + """ + Read the response's bytes. + + """ + try: + return self._content + except AttributeError: + self._validate_streaming_access() + self._content = ( + self._internal_response.body() or + b"".join(self.iter_raw()) + ) + self._close_stream() + return self._content + + def iter_bytes(self, chunk_size=None): + # type: (int) -> Iterator[bytes] + """Iterate over the bytes in the response stream + """ + try: + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), chunk_size): + yield self._content[i: i + chunk_size] + + except AttributeError: + for raw_bytes in self.iter_raw(chunk_size=chunk_size): + yield raw_bytes + + def iter_text(self, chunk_size=None): + # type: (int) -> Iterator[str] + """Iterate over the response text + """ + for byte in self.iter_bytes(chunk_size): + text = byte.decode(self.encoding or "utf-8") + yield text + + def iter_lines(self, chunk_size=None): + # type: (int) -> Iterator[str] + for text in self.iter_text(chunk_size): + lines = _parse_lines_from_text(text) + for line in lines: + yield line + + def _close_stream(self): + # type: (...) -> None + self.is_stream_consumed = True + self.close() + + def iter_raw(self, chunk_size=None): + # type: (int) -> Iterator[bytes] + """Iterate over the raw response bytes + """ + self._validate_streaming_access() + stream_download = self._internal_response.stream_download(None) + for raw_bytes in stream_download: + self._num_bytes_downloaded += len(raw_bytes) + yield raw_bytes + + self._close_stream() + +########################### ERRORS SECTION ################################# + +class StreamConsumedError(Exception): + def __init__(self): + message = ( + "You are attempting to read or stream content that has already been streamed. " + "You have likely already consumed this stream, so it can not be accessed anymore." + ) + super(StreamConsumedError, self).__init__(message) + +class ResponseClosedError(Exception): + def __init__(self): + message = ( + "You can not try to read or stream this response's content, since the " + "response has been closed." + ) + super(ResponseClosedError, self).__init__(message) + +class ResponseNotReadError(Exception): + + def __init__(self): + message = ( + "You have not read in the response's bytes yet. Call response.read() first." + ) + super(ResponseNotReadError, self).__init__(message) \ No newline at end of file diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py new file mode 100644 index 000000000000..2725aed72221 --- /dev/null +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py @@ -0,0 +1,716 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- +import asyncio +import os +import binascii +import codecs +import cgi +import json +from enum import Enum +import xml.etree.ElementTree as ET +from typing import ( + Any, + AsyncIterable, + IO, + Iterable, Iterator, + Optional, + Type, + Union, + Mapping, + Sequence, + Tuple, + List, +) +from abc import abstractmethod +from azure.core.exceptions import HttpResponseError + +################################### TYPES SECTION ######################### + +ByteStream = Union[Iterable[bytes], AsyncIterable[bytes]] +PrimitiveData = Optional[Union[str, int, float, bool]] + + +ParamsType = Union[ + Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]], + List[Tuple[str, PrimitiveData]] +] + +HeadersType = Union[ + Mapping[str, str], + Sequence[Tuple[str, str]] +] + +ContentType = Union[str, bytes, ByteStream] + +FileContent = Union[str, bytes, IO[str], IO[bytes]] +FileType = Union[ + Tuple[Optional[str], FileContent], +] + +FilesType = Union[ + Mapping[str, FileType], + Sequence[Tuple[str, FileType]] +] + +from azure.core.pipeline import Pipeline +from azure.core.pipeline.transport import ( + HttpRequest as _PipelineTransportHttpRequest, +) + +from azure.core.pipeline.transport._base import ( + _HttpResponseBase as _PipelineTransportHttpResponseBase +) + +from azure.core._pipeline_client import PipelineClient as _PipelineClient +from azure.core._pipeline_client_async import AsyncPipelineClient as _AsyncPipelineClient + +class HttpVerbs(str, Enum): + GET = "GET" + PUT = "PUT" + POST = "POST" + HEAD = "HEAD" + PATCH = "PATCH" + DELETE = "DELETE" + MERGE = "MERGE" + +########################### UTILS SECTION ################################# + +def _is_stream_or_str_bytes(content: Any) -> bool: + return isinstance(content, (str, bytes)) or any( + hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] + ) + +def _lookup_encoding(encoding: str) -> bool: + # including check for whether encoding is known taken from httpx + try: + codecs.lookup(encoding) + return True + except LookupError: + return False + +def _set_content_length_header(header_name: str, header_value: str, internal_request: _PipelineTransportHttpRequest) -> None: + valid_methods = ["put", "post", "patch"] + content_length_headers = ["Content-Length", "Transfer-Encoding"] + if ( + internal_request.method.lower() in valid_methods and + not any([c for c in content_length_headers if c in internal_request.headers]) + ): + internal_request.headers[header_name] = header_value + +def _set_content_type_header(header_value: str, internal_request: _PipelineTransportHttpRequest) -> None: + if not internal_request.headers.get("Content-Type"): + internal_request.headers["Content-Type"] = header_value + +def _set_content_body(content: ContentType, internal_request: _PipelineTransportHttpRequest) -> None: + headers = internal_request.headers + content_type = headers.get("Content-Type") + if _is_stream_or_str_bytes(content): + # stream will be bytes / str, or iterator of bytes / str + internal_request.set_streamed_data_body(content) + if isinstance(content, str) and content: + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_type_header("text/plain", internal_request) + elif isinstance(content, bytes) and content: + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_type_header("application/octet-stream", internal_request) + elif isinstance(content, (Iterable, AsyncIterable)): + _set_content_length_header("Transfer-Encoding", "chunked", internal_request) + _set_content_type_header("application/octet-stream", internal_request) + elif isinstance(content, ET.Element): + # XML body + internal_request.set_xml_body(content) + _set_content_type_header("application/xml", internal_request) + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + elif content_type and content_type.startswith("text/"): + # Text body + internal_request.set_text_body(content) + _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + else: + # Other body + internal_request.data = content + internal_request.headers = headers + +def _set_body( + content: ContentType, data: dict, files: Any, json_body: Any, internal_request: _PipelineTransportHttpRequest +) -> None: + if data is not None and not isinstance(data, dict): + content = data + data = None + if content is not None: + _set_content_body(content, internal_request) + elif json_body is not None: + internal_request.set_json_body(json_body) + _set_content_type_header("application/json", internal_request) + elif files is not None: + internal_request.set_formdata_body(files) + # if you don't supply your content type, we'll create a boundary for you with multipart/form-data + boundary = binascii.hexlify(os.urandom(16)).decode("ascii") # got logic from httpx, thanks httpx! + # _set_content_type_header("multipart/form-data; boundary={}".format(boundary), internal_request) + elif data: + _set_content_type_header("application/x-www-form-urlencoded", internal_request) + internal_request.set_formdata_body(data) + # need to set twice because Content-Type is being popped in set_formdata_body + # don't want to risk changing pipeline.transport, so doing twice here + _set_content_type_header("application/x-www-form-urlencoded", internal_request) + +def _parse_lines_from_text(text): + # largely taken from httpx's LineDecoder code + lines = [] + last_chunk_of_text = "" + while text: + text_length = len(text) + for idx in range(text_length): + curr_char = text[idx] + next_char = None if idx == len(text) - 1 else text[idx + 1] + if curr_char == "\n": + lines.append(text[: idx + 1]) + text = text[idx + 1: ] + break + if curr_char == "\r" and next_char == "\n": + # if it ends with \r\n, we only do \n + lines.append(text[:idx] + "\n") + text = text[idx + 2:] + break + if curr_char == "\r" and next_char is not None: + # if it's \r then a normal character, we switch \r to \n + lines.append(text[:idx] + "\n") + text = text[idx + 1:] + break + if next_char is None: + text = "" + last_chunk_of_text += text + break + if last_chunk_of_text.endswith("\r"): + # if ends with \r, we switch \r to \n + lines.append(last_chunk_of_text[:-1] + "\n") + elif last_chunk_of_text: + lines.append(last_chunk_of_text) + return lines + + +class _StreamContextManagerBase: + def __init__( + self, + client: Union[_PipelineClient, _AsyncPipelineClient], + request: "HttpRequest", + **kwargs + ): + """Used so we can treat stream requests and responses as a context manager. + + In Autorest, we only return a `StreamContextManager` if users pass in `stream_response` True + + Actually sends request when we enter the context manager, closes response when we exit. + + Heavily inspired from httpx, we want the same behavior for it to feel consistent for users + """ + self.client = client + self.request = request + self.kwargs = kwargs + + @abstractmethod + def close(self): + ... + +class _StreamContextManager(_StreamContextManagerBase): + def __enter__(self) -> "HttpResponse": + """Actually make the call only when we enter. For sync stream_response calls""" + pipeline_transport_response = self.client._pipeline.run( + self.request._internal_request, + stream=True, + **self.kwargs + ).http_response + self.response = HttpResponse( + request=self.request, + _internal_response=pipeline_transport_response + ) + return self.response + + def __exit__(self, *args): + """Close our stream connection. For sync calls""" + self.response.__exit__(*args) + + def close(self): + self.response.close() + +class _AsyncStreamContextManager(_StreamContextManagerBase): + async def __aenter__(self) -> "AsyncHttpResponse": + """Actually make the call only when we enter. For async stream_response calls.""" + if not isinstance(self.client, _AsyncPipelineClient): + raise TypeError( + "Only sync calls should enter here. If you mean to do a sync call, " + "make sure to use 'with' instead." + ) + pipeline_transport_response = (await self.client._pipeline.run( + self.request._internal_request, + stream=True, + **self.kwargs + )).http_response + self.response = AsyncHttpResponse( + request=self.request, + _internal_response=pipeline_transport_response + ) + return self.response + + async def __aexit__(self, *args): + await self.response.__aexit__(*args) + + async def close(self): + await self.response.close() + +################################## CLASSES ###################################### + +class HttpRequest: + """Represents an HTTP request. + + :param method: HTTP method (GET, HEAD, etc.) + :type method: str or ~azure.core.protocol.HttpVerbs + :param str url: The url for your request + :keyword params: Query parameters to be mapped into your URL. Your input + should be a mapping or sequence of query name to query value(s). + :paramtype params: mapping or sequence + :keyword headers: HTTP headers you want in your request. Your input should + be a mapping or sequence of header name to header value. + :paramtype headers: mapping or sequence + :keyword any json: A JSON serializable object. We handle JSON-serialization for your + object, so use this for more complicated data structures than `data`. + :keyword content: Content you want in your request body. Think of it as the kwarg you should input + if your data doesn't fit into `json`, `data`, or `files`. Accepts a bytes type, or a generator + that yields bytes. + :paramtype content: str or bytes or iterable[bytes] or asynciterable[bytes] + :keyword dict data: Form data you want in your request body. Use for form-encoded data, i.e. + HTML forms. + :keyword files: Files you want to in your request body. Use for uploading files with + multipart encoding. Your input should be a mapping or sequence of file name to file content. + Use the `data` kwarg in addition if you want to include non-file data files as part of your request. + :paramtype files: mapping or sequence + :ivar str url: The URL this request is against. + :ivar str method: The method type of this request. + :ivar headers: The HTTP headers you passed in to your request + :vartype headers: mapping or sequence + :ivar bytes content: The content passed in for the request + """ + + def __init__( + self, + method: str, + url: str, + *, + params: Optional[ParamsType] = None, + headers: Optional[HeadersType] = None, + json: Any = None, + content: Optional[ContentType] = None, + data: Optional[dict] = None, + files: Optional[FilesType] = None, + **kwargs + ): + # type: (str, str, Any) -> None + + self._internal_request = kwargs.pop("_internal_request", _PipelineTransportHttpRequest( + method=method, + url=url, + headers=headers, + )) + + if params: + self._internal_request.format_parameters(params) + + _set_body( + content=content, + data=data, + files=files, + json_body=json, + internal_request=self._internal_request + ) + + if kwargs: + raise TypeError( + "You have passed in kwargs '{}' that are not valid kwargs.".format( + "', '".join(list(kwargs.keys())) + ) + ) + + def _set_content_length_header(self) -> None: + method_check = self._internal_request.method.lower() in ["put", "post", "patch"] + content_length_unset = "Content-Length" not in self._internal_request.headers + if method_check and content_length_unset: + self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) + + @property + def url(self) -> str: + return self._internal_request.url + + @url.setter + def url(self, val: str) -> None: + self._internal_request.url = val + + @property + def method(self) -> str: + return self._internal_request.method + + @property + def headers(self) -> HeadersType: + return self._internal_request.headers + + @property + def content(self) -> Any: + """Gets the request content. + """ + return self._internal_request.data or self._internal_request.files + + def __repr__(self) -> str: + return self._internal_request.__repr__() + + def __deepcopy__(self, memo=None) -> "HttpRequest": + return HttpRequest( + self.method, + self.url, + _internal_request=self._internal_request.__deepcopy__(memo) + ) + +class _HttpResponseBase: + """Base class for HttpResponse and AsyncHttpResponse. + + :keyword request: The request that resulted in this response. + :paramtype request: ~azure.core.rest.HttpRequest + :ivar int status_code: The status code of this response + :ivar headers: The response headers + :vartype headers: dict[str, any] + :ivar str reason: The reason phrase for this response + :ivar bytes content: The response content in bytes + :ivar str url: The URL that resulted in this response + :ivar str encoding: The response encoding. Is settable, by default + is the response Content-Type header + :ivar str text: The response body as a string. + :ivar request: The request that resulted in this response. + :vartype request: ~azure.core.rest.HttpRequest + :ivar str content_type: The content type of the response + :ivar bool is_closed: Whether the network connection has been closed yet + :ivar bool is_stream_consumed: When getting a stream response, checks + whether the stream has been fully consumed + :ivar int num_bytes_downloaded: The number of bytes in your stream that + have been downloaded + """ + + def __init__( + self, + *, + request: HttpRequest, + **kwargs + ): + self._internal_response = kwargs.pop("_internal_response") # type: _PipelineTransportHttpResponseBase + self._request = request + self.is_closed = False + self.is_stream_consumed = False + self._num_bytes_downloaded = 0 + + @property + def status_code(self) -> int: + """Returns the status code of the response""" + return self._internal_response.status_code + + @status_code.setter + def status_code(self, val: int) -> None: + """Set the status code of the response""" + self._internal_response.status_code = val + + @property + def headers(self) -> HeadersType: + """Returns the response headers""" + return self._internal_response.headers + + @property + def reason(self) -> str: + """Returns the reason phrase for the response""" + return self._internal_response.reason + + @property + def content(self) -> bytes: + """Returns the response content in bytes""" + raise NotImplementedError() + + @property + def url(self) -> str: + """Returns the URL that resulted in this response""" + return self._internal_response.request.url + + @property + def encoding(self) -> str: + """Returns the response encoding. By default, is specified + by the response Content-Type header. + """ + + try: + return self._encoding + except AttributeError: + return self._get_charset_encoding() + + def _get_charset_encoding(self) -> str: + content_type = self.headers.get("Content-Type") + + if not content_type: + return None + _, params = cgi.parse_header(content_type) + encoding = params.get('charset') # -> utf-8 + if encoding is None or not _lookup_encoding(encoding): + return None + return encoding + + @encoding.setter + def encoding(self, value: str) -> None: + # type: (str) -> None + """Sets the response encoding""" + self._encoding = value + + @property + def text(self) -> str: + """Returns the response body as a string""" + self.content # access content to make sure we trigger if response not fully read in + return self._internal_response.text(encoding=self.encoding) + + @property + def request(self) -> HttpRequest: + if self._request: + return self._request + raise RuntimeError( + "You are trying to access the 'request', but there is no request associated with this HttpResponse" + ) + + @request.setter + def request(self, val: HttpRequest) -> None: + self._request = val + + @property + def content_type(self) -> Optional[str]: + """Content Type of the response""" + return self._internal_response.content_type or self.headers.get("Content-Type") + + @property + def num_bytes_downloaded(self) -> int: + """See how many bytes of your stream response have been downloaded""" + return self._num_bytes_downloaded + + def json(self) -> Any: + """Returns the whole body as a json object. + + :return: The JSON deserialized response body + :rtype: any + :raises json.decoder.JSONDecodeError or ValueError (in python 2.7) if object is not JSON decodable: + """ + return json.loads(self.text) + + def raise_for_status(self) -> None: + """Raises an HttpResponseError if the response has an error status code. + + If response is good, does nothing. + """ + if self.status_code >= 400: + raise HttpResponseError(response=self) + + def __repr__(self) -> str: + content_type_str = ( + ", Content-Type: {}".format(self.content_type) if self.content_type else "" + ) + return "<{}: {} {}{}>".format( + type(self).__name__, self.status_code, self.reason, content_type_str + ) + + def _validate_streaming_access(self) -> None: + if self.is_closed: + raise ResponseClosedError() + if self.is_stream_consumed: + raise StreamConsumedError() + +class HttpResponse(_HttpResponseBase): + + @property + def content(self): + # type: (...) -> bytes + try: + return self._content + except AttributeError: + raise ResponseNotReadError() + + def close(self) -> None: + self.is_closed = True + self._internal_response.internal_response.close() + + def __exit__(self, *args) -> None: + self._internal_response.internal_response.__exit__(*args) + + def read(self) -> bytes: + """ + Read the response's bytes. + + """ + try: + return self._content + except AttributeError: + self._validate_streaming_access() + self._content = ( + self._internal_response.body() or + b"".join(self.iter_raw()) + ) + self._close_stream() + return self._content + + def iter_bytes(self, chunk_size: int = None) -> Iterator[bytes]: + """Iterate over the bytes in the response stream + """ + try: + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), chunk_size): + yield self._content[i: i + chunk_size] + + except AttributeError: + for raw_bytes in self.iter_raw(chunk_size=chunk_size): + yield raw_bytes + + def iter_text(self, chunk_size: int = None) -> Iterator[str]: + """Iterate over the response text + """ + for byte in self.iter_bytes(chunk_size): + text = byte.decode(self.encoding or "utf-8") + yield text + + def iter_lines(self, chunk_size: int = None) -> Iterator[str]: + for text in self.iter_text(chunk_size): + lines = _parse_lines_from_text(text) + for line in lines: + yield line + + def _close_stream(self) -> None: + self.is_stream_consumed = True + self.close() + + def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: + """Iterate over the raw response bytes + """ + self._validate_streaming_access() + stream_download = self._internal_response.stream_download(None) + for raw_bytes in stream_download: + self._num_bytes_downloaded += len(raw_bytes) + yield raw_bytes + + self._close_stream() + + +class AsyncHttpResponse(_HttpResponseBase): + + @property + def content(self) -> bytes: + try: + return self._content + except AttributeError: + raise ResponseNotReadError() + + async def _close_stream(self) -> None: + self.is_stream_consumed = True + await self.close() + + async def read(self) -> bytes: + """ + Read the response's bytes. + + """ + try: + return self._content + except AttributeError: + self._validate_streaming_access() + await self._internal_response.load_body() + self._content = self._internal_response._body + await self._close_stream() + return self._content + + async def iter_bytes(self, chunk_size: int = None) -> Iterator[bytes]: + """Iterate over the bytes in the response stream + """ + try: + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), chunk_size): + yield self._content[i: i + chunk_size] + + except AttributeError: + async for raw_bytes in self.iter_raw(chunk_size=chunk_size): + yield raw_bytes + + async def iter_text(self, chunk_size: int = None) -> Iterator[str]: + """Iterate over the response text + """ + async for byte in self.iter_bytes(chunk_size): + text = byte.decode(self.encoding or "utf-8") + yield text + + async def iter_lines(self, chunk_size: int = None) -> Iterator[str]: + async for text in self.iter_text(chunk_size): + lines = _parse_lines_from_text(text) + for line in lines: + yield line + + async def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: + """Iterate over the raw response bytes + """ + self._validate_streaming_access() + stream_download = self._internal_response.stream_download(None) + async for raw_bytes in stream_download: + self._num_bytes_downloaded += len(raw_bytes) + yield raw_bytes + + await self._close_stream() + + async def close(self) -> None: + self.is_closed = True + self._internal_response.internal_response.close() + await asyncio.sleep(0) + + async def __aexit__(self, *args) -> None: + await self._internal_response.internal_response.__aexit__(*args) + + +########################### ERRORS SECTION ################################# + +class StreamConsumedError(Exception): + def __init__(self) -> None: + message = ( + "You are attempting to read or stream content that has already been streamed. " + "You have likely already consumed this stream, so it can not be accessed anymore." + ) + super().__init__(message) + +class ResponseClosedError(Exception): + def __init__(self) -> None: + message = ( + "You can not try to read or stream this response's content, since the " + "response has been closed." + ) + super().__init__(message) + +class ResponseNotReadError(Exception): + + def __init__(self) -> None: + message = ( + "You have not read in the response's bytes yet. Call response.read() first." + ) + super().__init__(message) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py index 3da4a3b958c6..3ec77a5e1375 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py @@ -13,7 +13,6 @@ 'build_group_exists_request', 'build_check_permission_request', 'build_user_exists_request', - 'build_user_exists_in_group_request', 'build_close_client_connection_request', 'build_grant_permission_request', 'build_healthapi_get_health_status_request', @@ -48,15 +47,15 @@ def build_healthapi_get_health_status_request( Get service health status. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/health') @@ -83,40 +82,45 @@ def build_send_to_all_request( Broadcast content inside request body to all the connected client connections. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. :type hub: str :keyword json: The payload body. - :paramtype json: any + :paramtype json: Any :keyword content: The payload body. :paramtype content: IO :keyword excluded: Excluded connection Ids. :paramtype excluded: list[str] :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest + + Example: + .. code-block:: python + + # JSON input template you can fill out and use as your `json` input. + json = "Any (optional)" """ excluded = kwargs.pop('excluded', None) # type: Optional[List[str]] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/:send') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), } url = _format_url_section(url, **path_format_arguments) # Construct parameters query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if excluded is not None: - query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') - if q is not None else '' for q in excluded] - + query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') if q is not None else '' for q in excluded] + if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') # Construct headers @@ -143,7 +147,7 @@ def build_connection_exists_request( Check if the connection with the given connectionId exists. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -152,17 +156,17 @@ def build_connection_exists_request( :type connection_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -189,7 +193,7 @@ def build_close_client_connection_request( Close the client connection. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -200,18 +204,18 @@ def build_close_client_connection_request( :paramtype reason: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ reason = kwargs.pop('reason', None) # type: Optional[str] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -240,7 +244,7 @@ def build_send_to_connection_request( Send content inside request body to the specific connection. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -248,23 +252,29 @@ def build_send_to_connection_request( :param connection_id: The connection Id. :type connection_id: str :keyword json: The payload body. - :paramtype json: any + :paramtype json: Any :keyword content: The payload body. :paramtype content: IO :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest + + Example: + .. code-block:: python + + # JSON input template you can fill out and use as your `json` input. + json = "Any (optional)" """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/connections/{connectionId}/:send') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -297,7 +307,7 @@ def build_group_exists_request( Check if there are any client connections inside the given group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -306,17 +316,17 @@ def build_group_exists_request( :type group: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -343,7 +353,7 @@ def build_send_to_group_request( Send content inside request body to a group of connections. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -351,39 +361,44 @@ def build_send_to_group_request( :param group: Target group name, which length should be greater than 0 and less than 1025. :type group: str :keyword json: The payload body. - :paramtype json: any + :paramtype json: Any :keyword content: The payload body. :paramtype content: IO :keyword excluded: Excluded connection Ids. :paramtype excluded: list[str] :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest + + Example: + .. code-block:: python + + # JSON input template you can fill out and use as your `json` input. + json = "Any (optional)" """ excluded = kwargs.pop('excluded', None) # type: Optional[List[str]] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/:send') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), } url = _format_url_section(url, **path_format_arguments) # Construct parameters query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] if excluded is not None: - query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') - if q is not None else '' for q in excluded] + query_parameters['excluded'] = [_SERIALIZER.query("excluded", q, 'str') if q is not None else '' for q in excluded] if api_version is not None: query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') # Construct headers - header_parameters = kwargs.get("headers", {}) # type: Dict[str, Any] + header_parameters = kwargs.pop("headers", {}) # type: Dict[str, Any] if content_type is not None: header_parameters['Content-Type'] = _SERIALIZER.header("content_type", content_type, 'str') @@ -407,7 +422,7 @@ def build_add_connection_to_group_request( Add a connection to the target group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -418,18 +433,18 @@ def build_add_connection_to_group_request( :type connection_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -457,7 +472,7 @@ def build_remove_connection_from_group_request( Remove a connection from the target group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -468,18 +483,18 @@ def build_remove_connection_from_group_request( :type connection_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/groups/{group}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -506,7 +521,7 @@ def build_user_exists_request( Check if there are any client connections connected for the given user. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -515,17 +530,17 @@ def build_user_exists_request( :type user_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -552,7 +567,7 @@ def build_send_to_user_request( Send content inside request body to the specific user. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -560,23 +575,29 @@ def build_send_to_user_request( :param user_id: The user Id. :type user_id: str :keyword json: The payload body. - :paramtype json: any + :paramtype json: Any :keyword content: The payload body. :paramtype content: IO :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest + + Example: + .. code-block:: python + + # JSON input template you can fill out and use as your `json` input. + json = "Any (optional)" """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] content_type = kwargs.pop("content_type", None) # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/:send') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -599,56 +620,6 @@ def build_send_to_user_request( ) -def build_user_exists_in_group_request( - hub, # type: str - group, # type: str - user_id, # type: str - **kwargs # type: Any -): - # type: (...) -> HttpRequest - """Check whether a user exists in the target group. - - Check whether a user exists in the target group. - - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. - - :param hub: Target hub name, which should start with alphabetic characters and only contain - alpha-numeric characters or underscore. - :type hub: str - :param group: Target group name, which length should be greater than 0 and less than 1025. - :type group: str - :param user_id: Target user Id. - :type user_id: str - :keyword api_version: Api Version. - :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest - """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] - - # Construct URL - url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') - path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), - } - url = _format_url_section(url, **path_format_arguments) - - # Construct parameters - query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] - if api_version is not None: - query_parameters['api-version'] = _SERIALIZER.query("api_version", api_version, 'str') - - return HttpRequest( - method="HEAD", - url=url, - params=query_parameters, - **kwargs - ) - - def build_add_user_to_group_request( hub, # type: str group, # type: str @@ -660,7 +631,7 @@ def build_add_user_to_group_request( Add a user to the target group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -671,18 +642,18 @@ def build_add_user_to_group_request( :type user_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -710,7 +681,7 @@ def build_remove_user_from_group_request( Remove a user from the target group. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -721,18 +692,18 @@ def build_remove_user_from_group_request( :type user_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups/{group}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'group': _SERIALIZER.url("group", group, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'group': _SERIALIZER.url("group", group, 'str', max_length=1024, min_length=1), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -759,7 +730,7 @@ def build_remove_user_from_all_groups_request( Remove a user from all groups. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -768,17 +739,17 @@ def build_remove_user_from_all_groups_request( :type user_id: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/users/{userId}/groups') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), - 'userId': _SERIALIZER.url("user_id", user_id, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), + 'userId': _SERIALIZER.url("user_id", user_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -806,7 +777,7 @@ def build_grant_permission_request( Grant permission to the connection. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. @@ -822,19 +793,19 @@ def build_grant_permission_request( :paramtype target_name: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ target_name = kwargs.pop('target_name', None) # type: Optional[str] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), 'permission': _SERIALIZER.url("permission", permission, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -864,14 +835,14 @@ def build_revoke_permission_request( Revoke permission for the connection. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. :type hub: str :param permission: The permission: current supported actions are joinLeaveGroup and sendToGroup. - :type permission: ~Permissions + :type permission: str or ~Permissions :param connection_id: Target connection Id. :type connection_id: str :keyword target_name: Optional. If not set, revoke the permission for all targets. If set, @@ -880,19 +851,19 @@ def build_revoke_permission_request( :paramtype target_name: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ target_name = kwargs.pop('target_name', None) # type: Optional[str] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), 'permission': _SERIALIZER.url("permission", permission, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -922,9 +893,9 @@ def build_check_permission_request( Check if a connection has permission to the specified action. - See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request_builder into your code flow. + See https://aka.ms/azsdk/python/llcwiki for how to incorporate this request builder into your code flow. - :param hub: Target hub name,q which should start with alphabetic characters and only contain + :param hub: Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. :type hub: str :param permission: The permission: current supported actions are joinLeaveGroup and @@ -938,19 +909,19 @@ def build_check_permission_request( :paramtype target_name: str :keyword api_version: Api Version. :paramtype api_version: str - :return: Returns an :class:`~azure.core.rest.HttpRequest` that you will pass to the client's `send_request` method. + :return: Returns an :class:`~azure.messaging.webpubsubservice.core.rest.HttpRequest` that you will pass to the client's `send_request` method. See https://aka.ms/azsdk/python/llcwiki for how to incorporate this response into your code flow. - :rtype: ~azure.core.rest.HttpRequest + :rtype: ~azure.messaging.webpubsubservice.core.rest.HttpRequest """ target_name = kwargs.pop('target_name', None) # type: Optional[str] - api_version = kwargs.pop('api_version', "2020-10-01") # type: Optional[str] + api_version = kwargs.pop('api_version', "2021-05-01-preview") # type: Optional[str] # Construct URL url = kwargs.pop("template_url", '/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}') path_format_arguments = { - 'hub': _SERIALIZER.url("hub", hub, 'str'), + 'hub': _SERIALIZER.url("hub", hub, 'str', pattern=r'^[A-Za-z][A-Za-z0-9_`,.[\]]{0,127}$'), 'permission': _SERIALIZER.url("permission", permission, 'str'), - 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str'), + 'connectionId': _SERIALIZER.url("connection_id", connection_id, 'str', min_length=1), } url = _format_url_section(url, **path_format_arguments) @@ -966,4 +937,4 @@ def build_check_permission_request( url=url, params=query_parameters, **kwargs - ) + ) \ No newline at end of file diff --git a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py index 099827cf6c60..92a6605bed3b 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py @@ -13,8 +13,10 @@ client = WebPubSubServiceClient(endpoint, credential=AzureKeyCredential(api_key), tracing_enabled=True) + # Send message to everybody on the given hub... -request = build_send_to_all_request('ahub', json={ 'Hello': 'all!' }) +request = build_send_to_all_request('skalman', json={ 'Hello': 'all!' }) +print(request.headers) response = client.send_request(request) try: response.raise_for_status() @@ -41,11 +43,9 @@ except: print('Failed to add user to group: {}'.format(response)) -request = build_user_exists_in_group_request('ahub', 'someGroup', 'me') -response = client.send_request(request) - request = build_add_connection_to_group_request('ahub', 'someGroup', '7') response = client.send_request(request) +response.read() print(response.content) # Add a connection to a group @@ -62,6 +62,14 @@ print('The group exists!') +request = build_grant_permission_request('skalman', 'sendToGroup', '7') +response = client.send_request(request) +response.read() +print(response.content) + + + request = build_grant_permission_request('ahub', 'sendToGroup', '7') response = client.send_request(request) -print(response.content) \ No newline at end of file +print(response.content) + From a73b5165c436077407927ed15a8207ec131565d9 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Fri, 23 Apr 2021 15:13:34 -0700 Subject: [PATCH 19/33] Fix encoding/decoding issue with pyjwt, removed decodepolicy --- .../azure/messaging/webpubsubservice/__init__.py | 4 ++-- .../azure/messaging/webpubsubservice/_policies.py | 5 ++--- sdk/signalr/azure-messaging-webpubsubservice/setup.py | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index 31ae2e050ed4..b7dd5b4b250c 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -16,6 +16,7 @@ from typing import TYPE_CHECKING import jwt +import six import azure.core.credentials as corecredentials import azure.core.pipeline as corepipeline @@ -87,7 +88,7 @@ def build_authentication_token(endpoint, hub, key, **kwargs): if roles: payload["role"] = roles - token = jwt.encode(payload, key, algorithm="HS256") + token = six.ensure_str(jwt.encode(payload, key, algorithm="HS256")) return { "baseUrl": client_url, "token": token, @@ -122,7 +123,6 @@ def __init__(self, endpoint, credential, **kwargs): corepolicies.CustomHookPolicy(**kwargs), corepolicies.RedirectPolicy(**kwargs), JwtCredentialPolicy(credential, kwargs.get("user", None)), - corepolicies.ContentDecodePolicy(**kwargs), corepolicies.NetworkTraceLoggingPolicy(**kwargs), ] # type: Any self._pipeline = corepipeline.Pipeline( diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py index ef79dd66c480..cb0c504ea790 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py @@ -27,6 +27,7 @@ import datetime import typing import jwt +import six from azure.core.pipeline.policies import SansIOHTTPPolicy @@ -81,6 +82,4 @@ def _encode(self, url): key=self._credential.key, algorithm="HS256", ) - if isinstance(encoded, bytes): - encoded = encoded.decode("utf8") - return typing.cast(str, encoded) # jwt's typing is incorrect... + return six.ensure_str(encoded) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/setup.py b/sdk/signalr/azure-messaging-webpubsubservice/setup.py index 914125ece0e8..7c1bd7d63a76 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/setup.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/setup.py @@ -67,6 +67,7 @@ "msrest>=0.6.18", "cryptography>=2.1.4", "pyjwt>=1.7.1", + "six>=1.12.0", ], extras_require={ ":python_version<'3.0'": ["futures", "azure-messaging-nspkg<2.0.0,>=1.0.0"], From a7e86f3123ee0dacff660c2dc0f82d243075c5fe Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Fri, 23 Apr 2021 15:16:59 -0700 Subject: [PATCH 20/33] Actually remove decode policy --- .../azure/messaging/webpubsubservice/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index 03dd1b54baa2..b7dd5b4b250c 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -123,7 +123,6 @@ def __init__(self, endpoint, credential, **kwargs): corepolicies.CustomHookPolicy(**kwargs), corepolicies.RedirectPolicy(**kwargs), JwtCredentialPolicy(credential, kwargs.get("user", None)), - corepolicies.ContentDecodePolicy(**kwargs), corepolicies.NetworkTraceLoggingPolicy(**kwargs), ] # type: Any self._pipeline = corepipeline.Pipeline( From fcb9b9e4f942c0acd12c95e5cc1d6d99080e0f98 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Fri, 23 Apr 2021 16:38:19 -0700 Subject: [PATCH 21/33] Changed to correct version of pylint, suppressed errors --- .../messaging/webpubsubservice/__init__.py | 47 ++-- .../messaging/webpubsubservice/_policies.py | 4 +- .../messaging/webpubsubservice/_utils.py | 1 + .../azure/messaging/webpubsubservice/aio.py | 14 +- .../webpubsubservice/core/rest/_rest.py | 144 +++++++----- .../webpubsubservice/core/rest/_rest_py3.py | 221 ++++++++++-------- .../azure/messaging/webpubsubservice/rest.py | 6 +- 7 files changed, 251 insertions(+), 186 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index b7dd5b4b250c..e32b5b93dee5 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -33,7 +33,8 @@ if TYPE_CHECKING: from azure.core.pipeline.policies import HTTPPolicy, SansIOHTTPPolicy from typing import Any, List, cast, Type, TypeVar - ClientType = TypeVar('ClientType', bound='WebPubSubServiceClient') + + ClientType = TypeVar("ClientType", bound="WebPubSubServiceClient") def build_authentication_token(endpoint, hub, key, **kwargs): @@ -65,7 +66,7 @@ def build_authentication_token(endpoint, hub, key, **kwargs): """ user = kwargs.pop("user", None) ttl = kwargs.pop("ttl", timedelta(hours=1)) - roles = kwargs.pop('roles', []) + roles = kwargs.pop("roles", []) endpoint = endpoint.lower() if not endpoint.startswith("http://") and not endpoint.startswith("https://"): raise ValueError( @@ -82,7 +83,11 @@ def build_authentication_token(endpoint, hub, key, **kwargs): client_url = "{}/client/hubs/{}".format(client_endpoint, hub) audience = "{}/client/hubs/{}".format(endpoint, hub) - payload = {"aud": audience, "iat": datetime.now(tz=_UTC), "exp": datetime.now(tz=_UTC) + ttl} + payload = { + "aud": audience, + "iat": datetime.now(tz=_UTC), + "exp": datetime.now(tz=_UTC) + ttl, + } if user: payload["sub"] = user if roles: @@ -114,7 +119,9 @@ def __init__(self, endpoint, credential, **kwargs): transport = kwargs.pop("transport", None) or coretransport.RequestsTransport( **kwargs ) - kwargs.setdefault('sdk_moniker', 'messaging-webpubsubservice/{}'.format(_VERSION)) + kwargs.setdefault( + "sdk_moniker", "messaging-webpubsubservice/{}".format(_VERSION) + ) policies = [ corepolicies.HeadersPolicy(**kwargs), corepolicies.UserAgentPolicy(**kwargs), @@ -130,7 +137,6 @@ def __init__(self, endpoint, credential, **kwargs): policies, ) # type: corepipeline.Pipeline - @classmethod def from_connection_string(cls, connection_string, **kwargs): # type: (Type[ClientType], str, Any) -> ClientType @@ -140,30 +146,35 @@ def from_connection_string(cls, connection_string, **kwargs): :type connection_string: ~str :rtype: WebPubSubServiceClient """ - for invalid_keyword_arg in ('endpoint', 'accesskey'): + for invalid_keyword_arg in ("endpoint", "accesskey"): if invalid_keyword_arg in kwargs: - raise TypeError('Unknown argument {}'.format(invalid_keyword_arg)) + raise TypeError("Unknown argument {}".format(invalid_keyword_arg)) for segment in connection_string.split(";"): - if '=' in segment: - key, value = segment.split('=', maxsplit=1) + if "=" in segment: + key, value = segment.split("=", maxsplit=1) key = key.lower() - if key == 'version': + if key == "version": # The name in the connection string != the name for the constructor. # Let's map it to whatthe constructor actually wants... - key = 'api_version' + key = "api_version" kwargs[key] = value elif segment: - raise ValueError("Malformed connection string - expected 'key=value', found segment '{}' in '{}'" - .format(segment, connection_string)) + raise ValueError( + "Malformed connection string - expected 'key=value', found segment '{}' in '{}'".format( + segment, connection_string + ) + ) - if 'endpoint' not in kwargs: + if "endpoint" not in kwargs: raise ValueError("connection_string missing 'endpoint' field") - if 'accesskey' not in kwargs: + if "accesskey" not in kwargs: raise ValueError("connection_string missing 'accesskey' field") - kwargs['credential'] = corecredentials.AzureKeyCredential(kwargs.pop('accesskey')) + kwargs["credential"] = corecredentials.AzureKeyCredential( + kwargs.pop("accesskey") + ) return cls(**kwargs) def __repr__(self): @@ -208,11 +219,11 @@ def send_request(self, http_request, **kwargs): # client=self._client, # request=request_copy, # ) - pipeline_response = self._pipeline.run(request_copy._internal_request, **kwargs) + pipeline_response = self._pipeline.run(request_copy._internal_request, **kwargs) # pylint: disable=protected-access response = corerest.HttpResponse( status_code=pipeline_response.http_response.status_code, request=request_copy, - _internal_response=pipeline_response.http_response + _internal_response=pipeline_response.http_response, ) response.read() return response diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py index cb0c504ea790..6dc11981689a 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_policies.py @@ -64,9 +64,7 @@ def on_request(self, request): request.http_request.headers["Authorization"] = "Bearer " + self._encode( request.http_request.url ) - return super(JwtCredentialPolicy, self).on_request( - request - ) + return super(JwtCredentialPolicy, self).on_request(request) def _encode(self, url): # type: (AzureKeyCredential) -> str diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_utils.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_utils.py index 84c5f51a56ca..042b46dd8848 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_utils.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/_utils.py @@ -26,6 +26,7 @@ import datetime + class _UTC_TZ(datetime.tzinfo): """from https://docs.python.org/2/library/datetime.html#tzinfo-objects""" diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py index a66958804a0a..0559416aa7bc 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/aio.py @@ -10,8 +10,6 @@ from typing import TYPE_CHECKING from copy import deepcopy -import jwt - import azure.core.pipeline as corepipeline import azure.core.pipeline.policies as corepolicies import azure.core.pipeline.transport as coretransport @@ -24,7 +22,7 @@ if TYPE_CHECKING: import azure.core.credentials as corecredentials from azure.core.pipeline.policies import HTTPPolicy, SansIOHTTPPolicy - from typing import Any, List, cast + from typing import Any, List, cast # pylint: disable=ungrouped-imports class WebPubSubServiceClient(object): @@ -65,7 +63,9 @@ def _format_url(self, url): assert self.endpoint[-1] != "/", "My endpoint should not have a trailing slash" return "/".join([self.endpoint, url.lstrip("/")]) - async def send_request(self, http_request: corerest.HttpRequest, **kwargs: Any) -> corerest.AsyncHttpResponse: + async def send_request( + self, http_request: corerest.HttpRequest, **kwargs: "Any" + ) -> corerest.AsyncHttpResponse: """Runs the network request through the client's chained policies. We have helper methods to create requests specific to this service in `azure.messaging.webpubsub.rest`. @@ -98,11 +98,13 @@ async def send_request(self, http_request: corerest.HttpRequest, **kwargs: Any) # client=self._client, # request=request_copy, # ) - pipeline_response = await self._pipeline.run(request_copy._internal_request, **kwargs) + pipeline_response = await self._pipeline.run( + request_copy._internal_request, **kwargs # pylint: disable=protected-access + ) response = corerest.AsyncHttpResponse( status_code=pipeline_response.http_response.status_code, request=request_copy, - _internal_response=pipeline_response.http_response + _internal_response=pipeline_response.http_response, ) await response.read() return response diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py index c40f6aa78195..39a98560031c 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py @@ -24,38 +24,45 @@ # # -------------------------------------------------------------------------- -from abc import abstractmethod -import sys -import six -import os -import binascii +# pylint currently complains about typing.Union not being subscriptable +# pylint: disable=unsubscriptable-object + import codecs -import cgi import json from enum import Enum import xml.etree.ElementTree as ET from typing import TYPE_CHECKING, Iterable +import cgi +import six + +from azure.core.exceptions import HttpResponseError from azure.core.pipeline.transport import ( HttpRequest as _PipelineTransportHttpRequest, ) + if TYPE_CHECKING: - from typing import ( - Any, Optional, Union, Mapping, Sequence, Tuple, Iterator + from typing import ( # pylint: disable=ungrouped-imports + Any, + Optional, + Union, + Mapping, + Sequence, + Tuple, + Iterator, ) + ByteStream = Iterable[bytes] - HeadersType = Union[ - Mapping[str, str], - Sequence[Tuple[str, str]] - ] + HeadersType = Union[Mapping[str, str], Sequence[Tuple[str, str]]] ContentType = Union[str, bytes, ByteStream] from azure.core.pipeline.transport._base import ( - _HttpResponseBase as _PipelineTransportHttpResponseBase + _HttpResponseBase as _PipelineTransportHttpResponseBase, ) from azure.core._pipeline_client import PipelineClient as _PipelineClient + class HttpVerbs(str, Enum): GET = "GET" PUT = "PUT" @@ -64,15 +71,17 @@ class HttpVerbs(str, Enum): PATCH = "PATCH" DELETE = "DELETE" MERGE = "MERGE" -from azure.core.exceptions import HttpResponseError + ########################### UTILS SECTION ################################# + def _is_stream_or_str_bytes(content): return isinstance(content, (str, bytes)) or any( hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] ) + def _lookup_encoding(encoding): # type: (str) -> bool # including check for whether encoding is known taken from httpx @@ -82,21 +91,23 @@ def _lookup_encoding(encoding): except LookupError: return False + def _set_content_length_header(header_name, header_value, internal_request): # type: (str, str, _PipelineTransportHttpRequest) -> None valid_methods = ["put", "post", "patch"] content_length_headers = ["Content-Length", "Transfer-Encoding"] - if ( - internal_request.method.lower() in valid_methods and - not any([c for c in content_length_headers if c in internal_request.headers]) + if internal_request.method.lower() in valid_methods and not any( + [c for c in content_length_headers if c in internal_request.headers] ): internal_request.headers[header_name] = header_value + def _set_content_type_header(header_value, internal_request): # type: (str, _PipelineTransportHttpRequest) -> None if not internal_request.headers.get("Content-Type"): internal_request.headers["Content-Type"] = header_value + def _set_content_body(content, internal_request): # type: (ContentType, _PipelineTransportHttpRequest) -> None headers = internal_request.headers @@ -105,28 +116,37 @@ def _set_content_body(content, internal_request): # stream will be bytes / str, or iterator of bytes / str internal_request.set_streamed_data_body(content) if isinstance(content, (str, bytes)) and content: - _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_length_header( + "Content-Length", str(len(internal_request.data)), internal_request + ) if isinstance(content, six.string_types): _set_content_type_header("text/plain", internal_request) else: _set_content_type_header("application/octet-stream", internal_request) - elif isinstance(content, Iterable): + elif isinstance( # pylint: disable=isinstance-second-argument-not-valid-type + content, Iterable + ): _set_content_length_header("Transfer-Encoding", "chunked", internal_request) _set_content_type_header("application/octet-stream", internal_request) elif isinstance(content, ET.Element): # XML body internal_request.set_xml_body(content) _set_content_type_header("application/xml", internal_request) - _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_length_header( + "Content-Length", str(len(internal_request.data)), internal_request + ) elif content_type and content_type.startswith("text/"): # Text body internal_request.set_text_body(content) - _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_length_header( + "Content-Length", str(len(internal_request.data)), internal_request + ) else: # Other body internal_request.data = content internal_request.headers = headers + def _set_body(content, data, files, json_body, internal_request): # type: (ContentType, dict, Any, Any, _PipelineTransportHttpRequest) -> None if data is not None and not isinstance(data, dict): @@ -140,7 +160,7 @@ def _set_body(content, data, files, json_body, internal_request): elif files is not None: internal_request.set_formdata_body(files) # if you don't supply your content type, we'll create a boundary for you with multipart/form-data - boundary = binascii.hexlify(os.urandom(16)).decode("ascii") # got logic from httpx, thanks httpx! + # boundary = binascii.hexlify(os.urandom(16)).decode("ascii") # got logic from httpx, thanks httpx! # _set_content_type_header("multipart/form-data; boundary={}".format(boundary), internal_request) elif data: _set_content_type_header("application/x-www-form-urlencoded", internal_request) @@ -149,6 +169,7 @@ def _set_body(content, data, files, json_body, internal_request): # don't want to risk changing pipeline.transport, so doing twice here _set_content_type_header("application/x-www-form-urlencoded", internal_request) + def _parse_lines_from_text(text): # largely taken from httpx's LineDecoder code lines = [] @@ -160,17 +181,17 @@ def _parse_lines_from_text(text): next_char = None if idx == len(text) - 1 else text[idx + 1] if curr_char == "\n": lines.append(text[: idx + 1]) - text = text[idx + 1: ] + text = text[idx + 1 :] break if curr_char == "\r" and next_char == "\n": # if it ends with \r\n, we only do \n lines.append(text[:idx] + "\n") - text = text[idx + 2:] + text = text[idx + 2 :] break if curr_char == "\r" and next_char is not None: # if it's \r then a normal character, we switch \r to \n lines.append(text[:idx] + "\n") - text = text[idx + 1:] + text = text[idx + 1 :] break if next_char is None: text = "" @@ -183,6 +204,7 @@ def _parse_lines_from_text(text): lines.append(last_chunk_of_text) return lines + ################################## CLASSES ###################################### class _StreamContextManager(object): def __init__(self, client, request, **kwargs): @@ -195,13 +217,10 @@ def __enter__(self): # type: (...) -> HttpResponse """Actually make the call only when we enter. For sync stream_response calls""" pipeline_transport_response = self.client._pipeline.run( - self.request._internal_request, - stream=True, - **self.kwargs + self.request._internal_request, stream=True, **self.kwargs ).http_response - self.response = HttpResponse( - request=self.request, - _internal_response=pipeline_transport_response + self.response = HttpResponse( # pylint: disable=attribute-defined-outside-init + request=self.request, _internal_response=pipeline_transport_response ) return self.response @@ -212,6 +231,7 @@ def __exit__(self, *args): def close(self): self.response.close() + class HttpRequest(object): """Represents an HTTP request. @@ -251,11 +271,14 @@ def __init__(self, method, url, **kwargs): json_body = kwargs.pop("json", None) files = kwargs.pop("files", None) - self._internal_request = kwargs.pop("_internal_request", _PipelineTransportHttpRequest( - method=method, - url=url, - headers=kwargs.pop("headers", None), - )) + self._internal_request = kwargs.pop( + "_internal_request", + _PipelineTransportHttpRequest( + method=method, + url=url, + headers=kwargs.pop("headers", None), + ), + ) params = kwargs.pop("params", None) if params: @@ -266,7 +289,7 @@ def __init__(self, method, url, **kwargs): data=data, files=files, json_body=json_body, - internal_request=self._internal_request + internal_request=self._internal_request, ) if kwargs: @@ -280,7 +303,9 @@ def _set_content_length_header(self): method_check = self._internal_request.method.lower() in ["put", "post", "patch"] content_length_unset = "Content-Length" not in self._internal_request.headers if method_check and content_length_unset: - self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) + self._internal_request.headers["Content-Length"] = str( + len(self._internal_request.data) + ) @property def url(self): @@ -305,8 +330,7 @@ def headers(self): @property def content(self): # type: (...) -> Any - """Gets the request content. - """ + """Gets the request content.""" return self._internal_request.data or self._internal_request.files def __repr__(self): @@ -316,9 +340,10 @@ def __deepcopy__(self, memo=None): return HttpRequest( self.method, self.url, - _internal_request=self._internal_request.__deepcopy__(memo) + _internal_request=self._internal_request.__deepcopy__(memo), ) + class _HttpResponseBase(object): """Base class for HttpResponse and AsyncHttpResponse. @@ -341,7 +366,9 @@ class _HttpResponseBase(object): def __init__(self, **kwargs): # type: (Any) -> None - self._internal_response = kwargs.pop("_internal_response") # type: _PipelineTransportHttpResponseBase + self._internal_response = kwargs.pop( + "_internal_response" + ) # type: _PipelineTransportHttpResponseBase self._request = kwargs.pop("request") self.is_closed = False self.is_stream_consumed = False @@ -401,7 +428,7 @@ def _get_charset_encoding(self): if not content_type: return None _, params = cgi.parse_header(content_type) - encoding = params.get('charset') # -> utf-8 + encoding = params.get("charset") # -> utf-8 if encoding is None or not _lookup_encoding(encoding): return None return encoding @@ -416,7 +443,9 @@ def encoding(self, value): def text(self): # type: (...) -> str """Returns the response body as a string""" - self.content # access content to make sure we trigger if response not fully read in + _ = ( + self.content + ) # access content to make sure we trigger if response not fully read in return self._internal_response.text(encoding=self.encoding) @property @@ -489,8 +518,8 @@ def _validate_streaming_access(self): if self.is_stream_consumed: raise StreamConsumedError() -class HttpResponse(_HttpResponseBase): +class HttpResponse(_HttpResponseBase): @property def content(self): # type: (...) -> bytes @@ -518,21 +547,19 @@ def read(self): return self._content except AttributeError: self._validate_streaming_access() - self._content = ( - self._internal_response.body() or - b"".join(self.iter_raw()) + self._content = ( # pylint: disable=attribute-defined-outside-init + self._internal_response.body() or b"".join(self.iter_raw()) ) self._close_stream() return self._content def iter_bytes(self, chunk_size=None): # type: (int) -> Iterator[bytes] - """Iterate over the bytes in the response stream - """ + """Iterate over the bytes in the response stream""" try: chunk_size = len(self._content) if chunk_size is None else chunk_size for i in range(0, len(self._content), chunk_size): - yield self._content[i: i + chunk_size] + yield self._content[i : i + chunk_size] except AttributeError: for raw_bytes in self.iter_raw(chunk_size=chunk_size): @@ -540,8 +567,7 @@ def iter_bytes(self, chunk_size=None): def iter_text(self, chunk_size=None): # type: (int) -> Iterator[str] - """Iterate over the response text - """ + """Iterate over the response text""" for byte in self.iter_bytes(chunk_size): text = byte.decode(self.encoding or "utf-8") yield text @@ -558,10 +584,9 @@ def _close_stream(self): self.is_stream_consumed = True self.close() - def iter_raw(self, chunk_size=None): + def iter_raw(self, **_): # type: (int) -> Iterator[bytes] - """Iterate over the raw response bytes - """ + """Iterate over the raw response bytes""" self._validate_streaming_access() stream_download = self._internal_response.stream_download(None) for raw_bytes in stream_download: @@ -570,8 +595,10 @@ def iter_raw(self, chunk_size=None): self._close_stream() + ########################### ERRORS SECTION ################################# + class StreamConsumedError(Exception): def __init__(self): message = ( @@ -580,6 +607,7 @@ def __init__(self): ) super(StreamConsumedError, self).__init__(message) + class ResponseClosedError(Exception): def __init__(self): message = ( @@ -588,10 +616,10 @@ def __init__(self): ) super(ResponseClosedError, self).__init__(message) -class ResponseNotReadError(Exception): +class ResponseNotReadError(Exception): def __init__(self): message = ( "You have not read in the response's bytes yet. Call response.read() first." ) - super(ResponseNotReadError, self).__init__(message) \ No newline at end of file + super(ResponseNotReadError, self).__init__(message) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py index 2725aed72221..fbcf603ef935 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py @@ -23,11 +23,13 @@ # IN THE SOFTWARE. # # -------------------------------------------------------------------------- + +# pylint currently complains about typing.Union not being subscriptable +# pylint: disable=unsubscriptable-object + + import asyncio -import os -import binascii import codecs -import cgi import json from enum import Enum import xml.etree.ElementTree as ET @@ -35,9 +37,9 @@ Any, AsyncIterable, IO, - Iterable, Iterator, + Iterable, + Iterator, Optional, - Type, Union, Mapping, Sequence, @@ -45,7 +47,22 @@ List, ) from abc import abstractmethod + +import cgi + from azure.core.exceptions import HttpResponseError +from azure.core.pipeline.transport import ( + HttpRequest as _PipelineTransportHttpRequest, +) + +from azure.core.pipeline.transport._base import ( + _HttpResponseBase as _PipelineTransportHttpResponseBase, +) + +from azure.core._pipeline_client import PipelineClient as _PipelineClient +from azure.core._pipeline_client_async import ( + AsyncPipelineClient as _AsyncPipelineClient, +) ################################### TYPES SECTION ######################### @@ -55,13 +72,10 @@ ParamsType = Union[ Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]], - List[Tuple[str, PrimitiveData]] + List[Tuple[str, PrimitiveData]], ] -HeadersType = Union[ - Mapping[str, str], - Sequence[Tuple[str, str]] -] +HeadersType = Union[Mapping[str, str], Sequence[Tuple[str, str]]] ContentType = Union[str, bytes, ByteStream] @@ -70,22 +84,9 @@ Tuple[Optional[str], FileContent], ] -FilesType = Union[ - Mapping[str, FileType], - Sequence[Tuple[str, FileType]] -] +FilesType = Union[Mapping[str, FileType], Sequence[Tuple[str, FileType]]] -from azure.core.pipeline import Pipeline -from azure.core.pipeline.transport import ( - HttpRequest as _PipelineTransportHttpRequest, -) -from azure.core.pipeline.transport._base import ( - _HttpResponseBase as _PipelineTransportHttpResponseBase -) - -from azure.core._pipeline_client import PipelineClient as _PipelineClient -from azure.core._pipeline_client_async import AsyncPipelineClient as _AsyncPipelineClient class HttpVerbs(str, Enum): GET = "GET" @@ -96,13 +97,16 @@ class HttpVerbs(str, Enum): DELETE = "DELETE" MERGE = "MERGE" + ########################### UTILS SECTION ################################# + def _is_stream_or_str_bytes(content: Any) -> bool: return isinstance(content, (str, bytes)) or any( hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"] ) + def _lookup_encoding(encoding: str) -> bool: # including check for whether encoding is known taken from httpx try: @@ -111,50 +115,71 @@ def _lookup_encoding(encoding: str) -> bool: except LookupError: return False -def _set_content_length_header(header_name: str, header_value: str, internal_request: _PipelineTransportHttpRequest) -> None: + +def _set_content_length_header( + header_name: str, header_value: str, internal_request: _PipelineTransportHttpRequest +) -> None: valid_methods = ["put", "post", "patch"] content_length_headers = ["Content-Length", "Transfer-Encoding"] - if ( - internal_request.method.lower() in valid_methods and - not any([c for c in content_length_headers if c in internal_request.headers]) + if internal_request.method.lower() in valid_methods and not any( + [c for c in content_length_headers if c in internal_request.headers] ): internal_request.headers[header_name] = header_value -def _set_content_type_header(header_value: str, internal_request: _PipelineTransportHttpRequest) -> None: + +def _set_content_type_header( + header_value: str, internal_request: _PipelineTransportHttpRequest +) -> None: if not internal_request.headers.get("Content-Type"): internal_request.headers["Content-Type"] = header_value -def _set_content_body(content: ContentType, internal_request: _PipelineTransportHttpRequest) -> None: + +def _set_content_body( + content: ContentType, internal_request: _PipelineTransportHttpRequest +) -> None: headers = internal_request.headers content_type = headers.get("Content-Type") if _is_stream_or_str_bytes(content): # stream will be bytes / str, or iterator of bytes / str internal_request.set_streamed_data_body(content) if isinstance(content, str) and content: - _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_length_header( + "Content-Length", str(len(internal_request.data)), internal_request + ) _set_content_type_header("text/plain", internal_request) elif isinstance(content, bytes) and content: - _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_length_header( + "Content-Length", str(len(internal_request.data)), internal_request + ) _set_content_type_header("application/octet-stream", internal_request) - elif isinstance(content, (Iterable, AsyncIterable)): + elif isinstance(content, (Iterable, AsyncIterable)): # pylint: disable=isinstance-second-argument-not-valid-type _set_content_length_header("Transfer-Encoding", "chunked", internal_request) _set_content_type_header("application/octet-stream", internal_request) elif isinstance(content, ET.Element): # XML body internal_request.set_xml_body(content) _set_content_type_header("application/xml", internal_request) - _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_length_header( + "Content-Length", str(len(internal_request.data)), internal_request + ) elif content_type and content_type.startswith("text/"): # Text body internal_request.set_text_body(content) - _set_content_length_header("Content-Length", str(len(internal_request.data)), internal_request) + _set_content_length_header( + "Content-Length", str(len(internal_request.data)), internal_request + ) else: # Other body internal_request.data = content internal_request.headers = headers + def _set_body( - content: ContentType, data: dict, files: Any, json_body: Any, internal_request: _PipelineTransportHttpRequest + content: ContentType, + data: dict, + files: Any, + json_body: Any, + internal_request: _PipelineTransportHttpRequest, ) -> None: if data is not None and not isinstance(data, dict): content = data @@ -167,7 +192,9 @@ def _set_body( elif files is not None: internal_request.set_formdata_body(files) # if you don't supply your content type, we'll create a boundary for you with multipart/form-data - boundary = binascii.hexlify(os.urandom(16)).decode("ascii") # got logic from httpx, thanks httpx! + # boundary = binascii.hexlify(os.urandom(16)).decode( + # "ascii" + #) # got logic from httpx, thanks httpx! # _set_content_type_header("multipart/form-data; boundary={}".format(boundary), internal_request) elif data: _set_content_type_header("application/x-www-form-urlencoded", internal_request) @@ -176,6 +203,7 @@ def _set_body( # don't want to risk changing pipeline.transport, so doing twice here _set_content_type_header("application/x-www-form-urlencoded", internal_request) + def _parse_lines_from_text(text): # largely taken from httpx's LineDecoder code lines = [] @@ -187,17 +215,17 @@ def _parse_lines_from_text(text): next_char = None if idx == len(text) - 1 else text[idx + 1] if curr_char == "\n": lines.append(text[: idx + 1]) - text = text[idx + 1: ] + text = text[idx + 1 :] break if curr_char == "\r" and next_char == "\n": # if it ends with \r\n, we only do \n lines.append(text[:idx] + "\n") - text = text[idx + 2:] + text = text[idx + 2 :] break if curr_char == "\r" and next_char is not None: # if it's \r then a normal character, we switch \r to \n lines.append(text[:idx] + "\n") - text = text[idx + 1:] + text = text[idx + 1 :] break if next_char is None: text = "" @@ -234,17 +262,15 @@ def __init__( def close(self): ... + class _StreamContextManager(_StreamContextManagerBase): def __enter__(self) -> "HttpResponse": """Actually make the call only when we enter. For sync stream_response calls""" pipeline_transport_response = self.client._pipeline.run( - self.request._internal_request, - stream=True, - **self.kwargs + self.request._internal_request, stream=True, **self.kwargs ).http_response - self.response = HttpResponse( - request=self.request, - _internal_response=pipeline_transport_response + self.response = HttpResponse( # pylint: disable=attribute-defined-outside-init + request=self.request, _internal_response=pipeline_transport_response ) return self.response @@ -255,6 +281,7 @@ def __exit__(self, *args): def close(self): self.response.close() + class _AsyncStreamContextManager(_StreamContextManagerBase): async def __aenter__(self) -> "AsyncHttpResponse": """Actually make the call only when we enter. For async stream_response calls.""" @@ -263,25 +290,26 @@ async def __aenter__(self) -> "AsyncHttpResponse": "Only sync calls should enter here. If you mean to do a sync call, " "make sure to use 'with' instead." ) - pipeline_transport_response = (await self.client._pipeline.run( - self.request._internal_request, - stream=True, - **self.kwargs - )).http_response - self.response = AsyncHttpResponse( - request=self.request, - _internal_response=pipeline_transport_response + pipeline_transport_response = ( + await self.client._pipeline.run( + self.request._internal_request, stream=True, **self.kwargs + ) + ).http_response + self.response = AsyncHttpResponse( # pylint: disable=attribute-defined-outside-init + request=self.request, _internal_response=pipeline_transport_response ) return self.response async def __aexit__(self, *args): await self.response.__aexit__(*args) - async def close(self): + async def close(self): # pylint: disable=invalid-overridden-method await self.response.close() + ################################## CLASSES ###################################### + class HttpRequest: """Represents an HTTP request. @@ -320,7 +348,7 @@ def __init__( *, params: Optional[ParamsType] = None, headers: Optional[HeadersType] = None, - json: Any = None, + json: Any = None, # pylint: disable=redefined-outer-name content: Optional[ContentType] = None, data: Optional[dict] = None, files: Optional[FilesType] = None, @@ -328,11 +356,14 @@ def __init__( ): # type: (str, str, Any) -> None - self._internal_request = kwargs.pop("_internal_request", _PipelineTransportHttpRequest( - method=method, - url=url, - headers=headers, - )) + self._internal_request = kwargs.pop( + "_internal_request", + _PipelineTransportHttpRequest( + method=method, + url=url, + headers=headers, + ), + ) if params: self._internal_request.format_parameters(params) @@ -342,7 +373,7 @@ def __init__( data=data, files=files, json_body=json, - internal_request=self._internal_request + internal_request=self._internal_request, ) if kwargs: @@ -356,7 +387,9 @@ def _set_content_length_header(self) -> None: method_check = self._internal_request.method.lower() in ["put", "post", "patch"] content_length_unset = "Content-Length" not in self._internal_request.headers if method_check and content_length_unset: - self._internal_request.headers["Content-Length"] = str(len(self._internal_request.data)) + self._internal_request.headers["Content-Length"] = str( + len(self._internal_request.data) + ) @property def url(self) -> str: @@ -376,8 +409,7 @@ def headers(self) -> HeadersType: @property def content(self) -> Any: - """Gets the request content. - """ + """Gets the request content.""" return self._internal_request.data or self._internal_request.files def __repr__(self) -> str: @@ -387,9 +419,10 @@ def __deepcopy__(self, memo=None) -> "HttpRequest": return HttpRequest( self.method, self.url, - _internal_request=self._internal_request.__deepcopy__(memo) + _internal_request=self._internal_request.__deepcopy__(memo), ) + class _HttpResponseBase: """Base class for HttpResponse and AsyncHttpResponse. @@ -414,13 +447,10 @@ class _HttpResponseBase: have been downloaded """ - def __init__( - self, - *, - request: HttpRequest, - **kwargs - ): - self._internal_response = kwargs.pop("_internal_response") # type: _PipelineTransportHttpResponseBase + def __init__(self, *, request: HttpRequest, **kwargs): + self._internal_response = kwargs.pop( + "_internal_response" + ) # type: _PipelineTransportHttpResponseBase self._request = request self.is_closed = False self.is_stream_consumed = False @@ -473,7 +503,7 @@ def _get_charset_encoding(self) -> str: if not content_type: return None _, params = cgi.parse_header(content_type) - encoding = params.get('charset') # -> utf-8 + encoding = params.get("charset") # -> utf-8 if encoding is None or not _lookup_encoding(encoding): return None return encoding @@ -487,7 +517,7 @@ def encoding(self, value: str) -> None: @property def text(self) -> str: """Returns the response body as a string""" - self.content # access content to make sure we trigger if response not fully read in + _ = self.content # access content to make sure we trigger if response not fully read in return self._internal_response.text(encoding=self.encoding) @property @@ -543,8 +573,8 @@ def _validate_streaming_access(self) -> None: if self.is_stream_consumed: raise StreamConsumedError() -class HttpResponse(_HttpResponseBase): +class HttpResponse(_HttpResponseBase): @property def content(self): # type: (...) -> bytes @@ -569,28 +599,24 @@ def read(self) -> bytes: return self._content except AttributeError: self._validate_streaming_access() - self._content = ( - self._internal_response.body() or - b"".join(self.iter_raw()) - ) + self._content = (self._internal_response.body() or # pylint: disable=attribute-defined-outside-init + b"".join(self.iter_raw())) self._close_stream() return self._content def iter_bytes(self, chunk_size: int = None) -> Iterator[bytes]: - """Iterate over the bytes in the response stream - """ + """Iterate over the bytes in the response stream""" try: chunk_size = len(self._content) if chunk_size is None else chunk_size for i in range(0, len(self._content), chunk_size): - yield self._content[i: i + chunk_size] + yield self._content[i : i + chunk_size] except AttributeError: for raw_bytes in self.iter_raw(chunk_size=chunk_size): yield raw_bytes def iter_text(self, chunk_size: int = None) -> Iterator[str]: - """Iterate over the response text - """ + """Iterate over the response text""" for byte in self.iter_bytes(chunk_size): text = byte.decode(self.encoding or "utf-8") yield text @@ -605,9 +631,8 @@ def _close_stream(self) -> None: self.is_stream_consumed = True self.close() - def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: - """Iterate over the raw response bytes - """ + def iter_raw(self, **_) -> Iterator[bytes]: + """Iterate over the raw response bytes""" self._validate_streaming_access() stream_download = self._internal_response.stream_download(None) for raw_bytes in stream_download: @@ -618,7 +643,6 @@ def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: class AsyncHttpResponse(_HttpResponseBase): - @property def content(self) -> bytes: try: @@ -640,25 +664,23 @@ async def read(self) -> bytes: except AttributeError: self._validate_streaming_access() await self._internal_response.load_body() - self._content = self._internal_response._body + self._content = self._internal_response._body # pylint: disable=protected-access,attribute-defined-outside-init await self._close_stream() return self._content async def iter_bytes(self, chunk_size: int = None) -> Iterator[bytes]: - """Iterate over the bytes in the response stream - """ + """Iterate over the bytes in the response stream""" try: chunk_size = len(self._content) if chunk_size is None else chunk_size for i in range(0, len(self._content), chunk_size): - yield self._content[i: i + chunk_size] + yield self._content[i : i + chunk_size] except AttributeError: async for raw_bytes in self.iter_raw(chunk_size=chunk_size): yield raw_bytes async def iter_text(self, chunk_size: int = None) -> Iterator[str]: - """Iterate over the response text - """ + """Iterate over the response text""" async for byte in self.iter_bytes(chunk_size): text = byte.decode(self.encoding or "utf-8") yield text @@ -669,9 +691,8 @@ async def iter_lines(self, chunk_size: int = None) -> Iterator[str]: for line in lines: yield line - async def iter_raw(self, chunk_size: int = None) -> Iterator[bytes]: - """Iterate over the raw response bytes - """ + async def iter_raw(self, **_) -> Iterator[bytes]: + """Iterate over the raw response bytes""" self._validate_streaming_access() stream_download = self._internal_response.stream_download(None) async for raw_bytes in stream_download: @@ -691,6 +712,7 @@ async def __aexit__(self, *args) -> None: ########################### ERRORS SECTION ################################# + class StreamConsumedError(Exception): def __init__(self) -> None: message = ( @@ -699,6 +721,7 @@ def __init__(self) -> None: ) super().__init__(message) + class ResponseClosedError(Exception): def __init__(self) -> None: message = ( @@ -707,8 +730,8 @@ def __init__(self) -> None: ) super().__init__(message) -class ResponseNotReadError(Exception): +class ResponseNotReadError(Exception): def __init__(self) -> None: message = ( "You have not read in the response's bytes yet. Call response.read() first." diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py index 3ec77a5e1375..9d57abd5a1c0 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/rest.py @@ -6,6 +6,8 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- +# pylint: disable=line-too-long + __all__ = [ 'build_add_connection_to_group_request', 'build_add_user_to_group_request', @@ -34,7 +36,7 @@ # pylint: disable=unused-import,ungrouped-imports from typing import Any, IO, List, Optional, Union, Dict from typing_extensions import Literal - Permissions = Union[Literal['joinLeaveGroup'], Literal['sendToGroup']] + Permissions = Union[Literal['joinLeaveGroup'], Literal['sendToGroup']] # pylint: disable=unsubscriptable-object _SERIALIZER = Serializer() @@ -937,4 +939,4 @@ def build_check_permission_request( url=url, params=query_parameters, **kwargs - ) \ No newline at end of file + ) From 3f4c547b2818243b4345ca7680de9a439e0ac89d Mon Sep 17 00:00:00 2001 From: scbedd <45376673+scbedd@users.noreply.github.com> Date: Fri, 23 Apr 2021 17:16:59 -0700 Subject: [PATCH 22/33] pushed ignore for pypi link in the readme. it'll come in later --- eng/ignore-links.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/ignore-links.txt b/eng/ignore-links.txt index eff0b385a379..5308ef654f79 100644 --- a/eng/ignore-links.txt +++ b/eng/ignore-links.txt @@ -2,4 +2,5 @@ https://docs.microsoft.com/python/api/overview/azure/{{package_doc_id}} https://github.com/Azure/azure-digital-twins/blob/private-preview/Documentation/how-to-manage-routes.md https://pypi.org/project/azure-digitaltwins-core https://docs.microsoft.com/azure/digital-twins/how-to-query-graph#query-limitations -https://docs.microsoft.com/samples/azure-samples/azure-samples-python-management/{{package_doc_id}} \ No newline at end of file +https://docs.microsoft.com/samples/azure-samples/azure-samples-python-management/{{package_doc_id}} +https://pypi.org/project/azure-messaging-webpubsubservice/ From 0bf467c923559eb4cdc66ef699fa886298e6515e Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Fri, 23 Apr 2021 17:48:11 -0700 Subject: [PATCH 23/33] Updated dev_requirement to local nspkg --- .../azure-messaging-webpubsubservice/dev_requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt b/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt index b3effab89e4d..7e186f5cb543 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt +++ b/sdk/signalr/azure-messaging-webpubsubservice/dev_requirements.txt @@ -1,6 +1,7 @@ -e ../../../tools/azure-devtools -e ../../../tools/azure-sdk-tools ../../core/azure-core --e ../../identity/azure-identity +../../identity/azure-identity +../../nspkg/azure-messaging-nspkg aiohttp>=3.0; python_version >= '3.5' typing_extensions>=3.7.2 From a0c012d0dadbbe5964e0b14602d4f5e6d3d4dc0e Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Fri, 23 Apr 2021 17:53:05 -0700 Subject: [PATCH 24/33] Fix structure of README.md --- sdk/signalr/azure-messaging-webpubsubservice/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/README.md b/sdk/signalr/azure-messaging-webpubsubservice/README.md index b92a9a831179..67534725c7ae 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/README.md +++ b/sdk/signalr/azure-messaging-webpubsubservice/README.md @@ -1,4 +1,4 @@ -# AzureWebPubSub Service client library for Python +# Azure WebPubSubService client library for Python [Azure Web PubSub Service](https://aka.ms/awps/doc) is a service that enables you to build real-time messaging web applications using WebSockets and the publish-subscribe pattern. Any platform supporting WebSocket APIs can connect to the service easily, e.g. web pages, mobile applications, edge devices, etc. The service manages the WebSocket connections for you and allows up to 100K \*concurrent connections. It provides powerful APIs for you to manage these clients and deliver real-time messages. @@ -53,6 +53,8 @@ In order to interact with the Azure WebPubSub service, you'll need to create an '> ``` +## Examples + ### Sending a request ```python @@ -112,6 +114,10 @@ logging.basicConfig(level=logging.DEBUG) Http request and response details are printed to stdout with this logging config. +## Next steps + +More examples are coming soon... + ## Contributing This project welcomes contributions and suggestions. Most contributions require From 6496bdc97a11f46b82a3be070eef8e397b969845 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Fri, 23 Apr 2021 18:27:37 -0700 Subject: [PATCH 25/33] Fix path for azure-messaging-nspkg --- .../azure-messaging-nspkg/{azure-messaging-nspkg => }/MANIFEST.in | 0 .../azure-messaging-nspkg/{azure-messaging-nspkg => }/README.md | 0 .../{azure-messaging-nspkg => }/azure/__init__.py | 0 .../{azure-messaging-nspkg => }/azure/messaging/__init__.py | 0 .../{azure-messaging-nspkg => }/sdk_packaging.toml | 0 .../azure-messaging-nspkg/{azure-messaging-nspkg => }/setup.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename sdk/nspkg/azure-messaging-nspkg/{azure-messaging-nspkg => }/MANIFEST.in (100%) rename sdk/nspkg/azure-messaging-nspkg/{azure-messaging-nspkg => }/README.md (100%) rename sdk/nspkg/azure-messaging-nspkg/{azure-messaging-nspkg => }/azure/__init__.py (100%) rename sdk/nspkg/azure-messaging-nspkg/{azure-messaging-nspkg => }/azure/messaging/__init__.py (100%) rename sdk/nspkg/azure-messaging-nspkg/{azure-messaging-nspkg => }/sdk_packaging.toml (100%) rename sdk/nspkg/azure-messaging-nspkg/{azure-messaging-nspkg => }/setup.py (100%) diff --git a/sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/MANIFEST.in b/sdk/nspkg/azure-messaging-nspkg/MANIFEST.in similarity index 100% rename from sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/MANIFEST.in rename to sdk/nspkg/azure-messaging-nspkg/MANIFEST.in diff --git a/sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/README.md b/sdk/nspkg/azure-messaging-nspkg/README.md similarity index 100% rename from sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/README.md rename to sdk/nspkg/azure-messaging-nspkg/README.md diff --git a/sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/azure/__init__.py b/sdk/nspkg/azure-messaging-nspkg/azure/__init__.py similarity index 100% rename from sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/azure/__init__.py rename to sdk/nspkg/azure-messaging-nspkg/azure/__init__.py diff --git a/sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/azure/messaging/__init__.py b/sdk/nspkg/azure-messaging-nspkg/azure/messaging/__init__.py similarity index 100% rename from sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/azure/messaging/__init__.py rename to sdk/nspkg/azure-messaging-nspkg/azure/messaging/__init__.py diff --git a/sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/sdk_packaging.toml b/sdk/nspkg/azure-messaging-nspkg/sdk_packaging.toml similarity index 100% rename from sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/sdk_packaging.toml rename to sdk/nspkg/azure-messaging-nspkg/sdk_packaging.toml diff --git a/sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/setup.py b/sdk/nspkg/azure-messaging-nspkg/setup.py similarity index 100% rename from sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/setup.py rename to sdk/nspkg/azure-messaging-nspkg/setup.py From 67644aab9dd0d2ec7ff1351e2728ecb9f78fc079 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Fri, 23 Apr 2021 19:38:04 -0700 Subject: [PATCH 26/33] Ignore README for azure-messaging-nspkg --- eng/.docsettings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/.docsettings.yml b/eng/.docsettings.yml index 8fc06fdefe5e..ff1a528376cc 100644 --- a/eng/.docsettings.yml +++ b/eng/.docsettings.yml @@ -145,7 +145,7 @@ known_content_issues: # HISTORY.rst - ['sdk/core/azure/HISTORY.rst','nspkg and common'] - ['sdk/digitaltwins/azure-digitaltwins-nspkg/README.md', 'nspkg and common'] - - ['sdk/nspkg/azure-messaging-nspkg/azure-messaging-nspkg/README.md', '#4554'] + - ['sdk/nspkg/azure-messaging-nspkg/README.md', '#4554'] # root readme - ['README.md', 'root readme'] From ce0c1b6b7b8d0f21cb3b24ba0f8a9cbd2a5874a8 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Fri, 23 Apr 2021 20:26:40 -0700 Subject: [PATCH 27/33] Update shared reqs --- shared_requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shared_requirements.txt b/shared_requirements.txt index a0589960000b..8e1d064c9b7f 100644 --- a/shared_requirements.txt +++ b/shared_requirements.txt @@ -86,6 +86,7 @@ azure-mgmt-storage~=2.0 azure-mgmt-subscription~=0.2.0 azure-mgmt-trafficmanager~=0.50.0 azure-mgmt-web~=0.35.0 +azure-messaging-nspkg azure-mixedreality-nspkg azure-nspkg azure-keyvault-nspkg @@ -201,6 +202,10 @@ opentelemetry-sdk<2.0.0,>=1.0.0 #override azure-core-tracing-opentelemetry opentelemetry-api<2.0.0,>=1.0.0 #override azure-identity six>=1.12.0 #override azure-keyvault-keys six>=1.12.0 +#override azure-messaging-webpubsub azure-core<2.0.0,>=1.10.0 +#override azure-messaging-webpubsub msrest>=0.6.18 +#override azure-messaging-webpubsub pyjwt>=1.7.1 +#override azure-messaging-webpubsub six>=1.12.0 #override azure-mixedreality-authentication azure-core<2.0.0,>=1.4.0 #override azure-iot-deviceupdate azure-core<2.0.0,>=1.6.0 #override azure-mgmt-changeanalysis msrest>=0.6.21 @@ -253,3 +258,4 @@ opentelemetry-sdk<2.0.0,>=1.0.0 #override azure-mgmt-managedservices msrest>=0.6.21 #override azure-purview-scanning azure-core<2.0.0,>=1.8.2 msrest>=0.6.21 #override azure-purview-catalog azure-core<2.0.0,>=1.8.2 msrest>=0.6.21 + From 421ba5391266f20914b865e45843c496b77724bb Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Sat, 24 Apr 2021 16:13:30 -0700 Subject: [PATCH 28/33] Updated examples --- .../webpubsubservice/core/rest/_rest.py | 2 +- .../webpubsubservice/core/rest/_rest_py3.py | 2 +- .../examples/send_messages.py | 75 +++++++------------ .../azure-messaging-webpubsubservice/setup.py | 2 - 4 files changed, 29 insertions(+), 52 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py index 39a98560031c..cee1c039cbda 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest.py @@ -126,7 +126,7 @@ def _set_content_body(content, internal_request): elif isinstance( # pylint: disable=isinstance-second-argument-not-valid-type content, Iterable ): - _set_content_length_header("Transfer-Encoding", "chunked", internal_request) + # _set_content_length_header("Transfer-Encoding", "chunked", internal_request) _set_content_type_header("application/octet-stream", internal_request) elif isinstance(content, ET.Element): # XML body diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py index fbcf603ef935..ed2908d69484 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/core/rest/_rest_py3.py @@ -153,7 +153,7 @@ def _set_content_body( ) _set_content_type_header("application/octet-stream", internal_request) elif isinstance(content, (Iterable, AsyncIterable)): # pylint: disable=isinstance-second-argument-not-valid-type - _set_content_length_header("Transfer-Encoding", "chunked", internal_request) + # _set_content_length_header("Transfer-Encoding", "chunked", internal_request) _set_content_type_header("application/octet-stream", internal_request) elif isinstance(content, ET.Element): # XML body diff --git a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py index 92a6605bed3b..96913de2dde1 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/examples/send_messages.py @@ -2,74 +2,53 @@ import logging import os -from azure.core.credentials import AzureKeyCredential from azure.messaging.webpubsubservice import WebPubSubServiceClient from azure.messaging.webpubsubservice.rest import * -endpoint = os.environ['ENDPOINT'] -api_key = os.environ['API_KEY'] +logging.basicConfig(level=logging.DEBUG) +LOG = logging.getLogger() -LOG = logging.basicConfig(level=logging.DEBUG) - -client = WebPubSubServiceClient(endpoint, credential=AzureKeyCredential(api_key), tracing_enabled=True) +try: + connection_string = os.environ['WEBPUBSUB_CONNECTION_STRING'] +except KeyError: + LOG.error("Missing environment variable 'WEBPUBSUB_CONNECTION_STRING' - please set if before running the example") + exit() +# Build a client from the connection string. And for this example, we have enabled debug +# tracing. For production code, this should be turned off. +client = WebPubSubServiceClient.from_connection_string(connection_string, tracing_enabled=True) -# Send message to everybody on the given hub... -request = build_send_to_all_request('skalman', json={ 'Hello': 'all!' }) +# Send a json message to everybody on the given hub... +request = build_send_to_all_request('myHub', json={ 'Hello': 'all!' }) print(request.headers) response = client.send_request(request) try: + # Raise an exception if the service rejected the call response.raise_for_status() - print('Successfully sent a message') + print('Successfully sent a JSON message') except: - print('Failed to send message: {}'.format(response)) + print('Failed to send JSON message: {}'.format(response)) +# Send a text message to everybody on the given hub... request = build_send_to_all_request('ahub', content='hello, text!', content_type='text/plain') -print(request.headers.items()) -response = client.send_request(request) -print(response) - - -request = build_send_to_all_request('ahub', content=io.BytesIO(b'{ "hello": "world" }'), content_type='application/json') -response = client.send_request(request) -print(response) - -# Add a user to a group -request = build_add_user_to_group_request('ahub', group='someGroup', user_id='me') response = client.send_request(request) try: + # Raise an exception if the service rejected the call response.raise_for_status() + print('Successfully sent a TEXT message') except: - print('Failed to add user to group: {}'.format(response)) + print('Failed to send TEXT message: {}'.format(response)) -request = build_add_connection_to_group_request('ahub', 'someGroup', '7') -response = client.send_request(request) -response.read() -print(response.content) -# Add a connection to a group -request = build_add_connection_to_group_request(hub='ahub', group='someGroup', connection_id='7') -response = client.send_request(request) -print(response) -# Check if a group exists -request = build_connection_exists_request(hub='ahub', connection_id='missing') -response = client.send_request(request) -if response.status_code == 404: - print("no such group") -else: - print('The group exists!') - - -request = build_grant_permission_request('skalman', 'sendToGroup', '7') -response = client.send_request(request) -response.read() -print(response.content) - - - -request = build_grant_permission_request('ahub', 'sendToGroup', '7') +# Send a json message from a stream to everybody on the given hub... +request = build_send_to_all_request('ahub', content=io.BytesIO(b'{ "hello": "world" }'), content_type='application/json') response = client.send_request(request) -print(response.content) +try: + # Raise an exception if the service rejected the call + response.raise_for_status() + print('Successfully sent a JSON message from a stream') +except: + print('Failed to send JSON message from a stream: {}'.format(response)) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/setup.py b/sdk/signalr/azure-messaging-webpubsubservice/setup.py index 7c1bd7d63a76..fab1d2511bed 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/setup.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/setup.py @@ -58,8 +58,6 @@ # Exclude packages that will be covered by PEP420 or nspkg "azure", "azure.messaging", - "tests", - "examples", ] ), install_requires=[ From 83237960db352793549105bd4bfefbc7e52e7942 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Sat, 24 Apr 2021 16:30:30 -0700 Subject: [PATCH 29/33] Allow pyjwt, other frozen dependencies for webpubsub --- shared_requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared_requirements.txt b/shared_requirements.txt index 8e1d064c9b7f..128cfc47b9f4 100644 --- a/shared_requirements.txt +++ b/shared_requirements.txt @@ -202,10 +202,10 @@ opentelemetry-sdk<2.0.0,>=1.0.0 #override azure-core-tracing-opentelemetry opentelemetry-api<2.0.0,>=1.0.0 #override azure-identity six>=1.12.0 #override azure-keyvault-keys six>=1.12.0 -#override azure-messaging-webpubsub azure-core<2.0.0,>=1.10.0 -#override azure-messaging-webpubsub msrest>=0.6.18 -#override azure-messaging-webpubsub pyjwt>=1.7.1 -#override azure-messaging-webpubsub six>=1.12.0 +#override azure-messaging-webpubsubservice azure-core<2.0.0,>=1.10.0 +#override azure-messaging-webpubsubservice msrest>=0.6.18 +#override azure-messaging-webpubsubservice pyjwt>=1.7.1 +#override azure-messaging-webpubsubservice six>=1.12.0 #override azure-mixedreality-authentication azure-core<2.0.0,>=1.4.0 #override azure-iot-deviceupdate azure-core<2.0.0,>=1.6.0 #override azure-mgmt-changeanalysis msrest>=0.6.21 From 4d43382cbaebb04306ba146a13b8adc9ecec9346 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Sat, 24 Apr 2021 16:33:12 -0700 Subject: [PATCH 30/33] Include examples in MANIFEST --- sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in b/sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in index 2094336fbe77..a3b68055b727 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in +++ b/sdk/signalr/azure-messaging-webpubsubservice/MANIFEST.in @@ -3,4 +3,4 @@ include azure/__init__.py include azure/messaging/__init__.py include LICENSE.txt recursive-include tests *.py -recursive-include samples *.py *.md \ No newline at end of file +recursive-include examples *.py *.md \ No newline at end of file From d4d3b5937a21424b6e4d4f3284d2a63c6fe68e0b Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Sat, 24 Apr 2021 16:47:28 -0700 Subject: [PATCH 31/33] Freeze pyjwt --- shared_requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/shared_requirements.txt b/shared_requirements.txt index 128cfc47b9f4..45b549957cb3 100644 --- a/shared_requirements.txt +++ b/shared_requirements.txt @@ -123,6 +123,7 @@ python-dateutil>=2.8.0 six>=1.11.0 isodate>=0.6.0 avro<2.0.0,>=1.10.0 +pyjwt>=1.7.1 #override azure azure-keyvault~=1.0 #override azure-mgmt-core azure-core<2.0.0,>=1.9.0 #override azure-containerregistry azure-core>=1.4.0,<2.0.0 From e9f9abd4e412db4f5a96cab12beb100c12a1d38d Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Sat, 24 Apr 2021 17:01:41 -0700 Subject: [PATCH 32/33] Open dependency range for nspkg deps. Cannot change --- sdk/signalr/azure-messaging-webpubsubservice/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/setup.py b/sdk/signalr/azure-messaging-webpubsubservice/setup.py index fab1d2511bed..7b4cee515a8a 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/setup.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/setup.py @@ -68,7 +68,7 @@ "six>=1.12.0", ], extras_require={ - ":python_version<'3.0'": ["futures", "azure-messaging-nspkg<2.0.0,>=1.0.0"], + ":python_version<'3.0'": ["futures", "azure-messaging-nspkg"], ":python_version<'3.5'": ["typing"], }, ) From 60fb473547d4888e76e64f294f77d6cdbd757b9d Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Mon, 26 Apr 2021 14:42:19 -0700 Subject: [PATCH 33/33] add support from build_authentication_token from connection strings --- .../messaging/webpubsubservice/__init__.py | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py index e32b5b93dee5..529b1f7e9d7d 100644 --- a/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py +++ b/sdk/signalr/azure-messaging-webpubsubservice/azure/messaging/webpubsubservice/__init__.py @@ -37,15 +37,37 @@ ClientType = TypeVar("ClientType", bound="WebPubSubServiceClient") -def build_authentication_token(endpoint, hub, key, **kwargs): +def _parse_connection_string(connection_string, **kwargs): + for segment in connection_string.split(";"): + if "=" in segment: + key, value = segment.split("=", maxsplit=1) + key = key.lower() + if key not in ("version", ): + kwargs.setdefault(key, value) + elif segment: + raise ValueError( + "Malformed connection string - expected 'key=value', found segment '{}' in '{}'".format( + segment, connection_string + ) + ) + + if "endpoint" not in kwargs: + raise ValueError("connection_string missing 'endpoint' field") + + if "accesskey" not in kwargs: + raise ValueError("connection_string missing 'accesskey' field") + + return kwargs + +def build_authentication_token(endpoint, hub, **kwargs): """Build an authentication token for the given endpoint, hub using the provided key. - :param endpoint: HTTP or HTTPS endpoint for the WebPubSub service instance. + :keyword endpoint: connetion string or HTTP or HTTPS endpoint for the WebPubSub service instance. :type endpoint: ~str - :param hub: The hub to give access to. + :keyword hub: The hub to give access to. :type hub: ~str - :param key: Key to sign the token with. - :type key: ~str + :keyword accesskey: Key to sign the token with. Required if endpoint is not a connection string + :type accesskey: ~str :keyword ttl: Optional ttl timedelta for the token. Default is 1 hour. :type ttl: ~datetime.timedelta :keyword user: Optional user name (subject) for the token. Default is no user. @@ -57,14 +79,19 @@ def build_authentication_token(endpoint, hub, key, **kwargs): Example: - >>> build_authentication_token('https://contoso.com/api/webpubsub', hub='theHub', key='123') + >>> build_authentication_token(endpoint='https://contoso.com/api/webpubsub', hub='theHub', key='123') { 'baseUrl': 'wss://contoso.com/api/webpubsub/client/hubs/theHub', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ...', 'url': 'wss://contoso.com/api/webpubsub/client/hubs/theHub?access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ...' } """ + if 'accesskey' not in kwargs: + kwargs = _parse_connection_string(endpoint, **kwargs) + endpoint = kwargs.pop('endpoint') + user = kwargs.pop("user", None) + key = kwargs.pop("accesskey") ttl = kwargs.pop("ttl", timedelta(hours=1)) roles = kwargs.pop("roles", []) endpoint = endpoint.lower() @@ -146,31 +173,7 @@ def from_connection_string(cls, connection_string, **kwargs): :type connection_string: ~str :rtype: WebPubSubServiceClient """ - for invalid_keyword_arg in ("endpoint", "accesskey"): - if invalid_keyword_arg in kwargs: - raise TypeError("Unknown argument {}".format(invalid_keyword_arg)) - - for segment in connection_string.split(";"): - if "=" in segment: - key, value = segment.split("=", maxsplit=1) - key = key.lower() - if key == "version": - # The name in the connection string != the name for the constructor. - # Let's map it to whatthe constructor actually wants... - key = "api_version" - kwargs[key] = value - elif segment: - raise ValueError( - "Malformed connection string - expected 'key=value', found segment '{}' in '{}'".format( - segment, connection_string - ) - ) - - if "endpoint" not in kwargs: - raise ValueError("connection_string missing 'endpoint' field") - - if "accesskey" not in kwargs: - raise ValueError("connection_string missing 'accesskey' field") + kwargs = _parse_connection_string(connection_string, **kwargs) kwargs["credential"] = corecredentials.AzureKeyCredential( kwargs.pop("accesskey")