8000 Core typings (#28114) · Azure/azure-sdk-for-python@8176b0f · GitHub
[go: up one dir, main page]

Skip to content

Commit 8176b0f

Browse files
lmazuelannatisch
andauthored
Core typings (#28114)
* Core typings * Restore mypy clean * Black it * Mypy+PyLint+Black * Remove object inheritance * dict/list are not typing before 3.9 * Runtime fixes * Forgotten List * Update _authentication.py * Replace more dict by Dict * More typing fixes * More Dict fixes * Fix List typing * Pyright Paging * Simplify some ABC import * Pylint clean-up * Fix typing for Identity * Keep 'next' in paging as this used in some Py3 code... * Form can have int values as well * Better annotations * Pylint has trouble on 3.7 * Type configuration * MatchCondition doc * PyLint happyness * Feedback from Kashif * Redesign some of the async pipeline types * Fix for 3.7 * Remove unecessary type * Feedback * Male kwarg only syntax * Correct enum doc * Full typing for CloudEvent * New feedback from Johan * More feedback * Feedback * Enabling mypy and typecheck * Improve policy typing * Improve response typing * Update sdk/core/azure-core/azure/core/pipeline/policies/_authentication.py Co-authored-by: Anna Tisch <antisch@microsoft.com> * Update sdk/core/azure-core/azure/core/_pipeline_client_async.py Co-authored-by: Anna Tisch <antisch@microsoft.com> * Update sdk/core/azure-core/azure/core/_pipeline_client_async.py Co-authored-by: Anna Tisch <antisch@microsoft.com> * Feedback * Black * Enum typing * Remove init for better typing * Fix typing * PyLint --------- Co-authored-by: Anna Tisch <antisch@microsoft.com>
1 parent e00bfa2 commit 8176b0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+913
-873
lines changed

sdk/core/azure-core/azure/core/_enum_meta.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
# IN THE SOFTWARE.
2424
#
2525
# --------------------------------------------------------------------------
26-
27-
from enum import EnumMeta
26+
from typing import Any
27+
from enum import EnumMeta, Enum
2828

2929

3030
class CaseInsensitiveEnumMeta(EnumMeta):
@@ -43,13 +43,13 @@ class MyCustomEnum(str, Enum, metaclass=CaseInsensitiveEnumMeta):
4343
4444
"""
4545

46-
def __getitem__(cls, name):
46+
def __getitem__(cls, name: str) -> Any:
4747
# disabling pylint bc of pylint bug https://github.com/PyCQA/astroid/issues/713
4848
return super( # pylint: disable=no-value-for-parameter
4949
CaseInsensitiveEnumMeta, cls
5050
).__getitem__(name.upper())
5151

52-
def __getattr__(cls, name):
52+
def __getattr__(cls, name: str) -> Enum:
5353
"""Return the enum member matching `name`
5454
We use __getattr__ instead of descriptors or inserting into the enum
5555
class' __dict__ in order to support `name` and `value` being both

sdk/core/azure-core/azure/core/_match_conditions.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,17 @@
3030
class MatchConditions(Enum):
3131
"""An enum to describe match conditions."""
3232

33-
Unconditionally = 1 # Matches any condition
34-
IfNotModified = 2 # If the target object is not modified. Usually it maps to etag=<specific etag>
35-
IfModified = 3 # Only if the target object is modified. Usually it maps to etag!=<specific etag>
36-
IfPresent = 4 # If the target object exists. Usually it maps to etag='*'
37-
IfMissing = 5 # If the target object does not exist. Usually it maps to etag!='*'
33+
Unconditionally = 1
34+
"""Matches any condition"""
35+
36+
IfNotModified = 2
37+
"""If the target object is not modified. Usually it maps to etag=<specific etag>"""
38+
39+
IfModified = 3
40+
"""Only if the target object is modified. Usually it maps to etag!=<specific etag>"""
41+
42+
IfPresent = 4
43+
"""If the target object exists. Usually it maps to etag='*'"""
44+
45+
IfMissing = 5
46+
"""If the target object does not exist. Usually it maps to etag!='*'"""

sdk/core/azure-core/azure/core/_pipeline_client.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import logging
2828
from collections.abc import Iterable
2929
from typing import (
30-
Any,
3130
TypeVar,
3231
TYPE_CHECKING,
3332
)
@@ -169,8 +168,7 @@ def _build_pipeline(self, config, **kwargs): # pylint: disable=no-self-use
169168

170169
return Pipeline(transport, policies)
171170

172-
def send_request(self, request, **kwargs):
173-
# type: (HTTPRequestType, Any) -> HTTPResponseType
171+
def send_request(self, request: "HTTPRequestType", **kwargs) -> "HTTPResponseType":
174172
"""Method that runs the network request through the client's chained policies.
175173
176174
>>> from azure.core.rest import HttpRequest

sdk/core/azure-core/azure/core/_pipeline_client_async.py

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,16 @@
2626

2727
import logging
2828
import collections.abc
29-
from typing import Any, Awaitable, TypeVar
29+
from typing import (
30+
Any,
31+
Awaitable,
32+
TypeVar,
33+
AsyncContextManager,
34+
Generator,
35+
cast,
36+
TYPE_CHECKING,
37+
)
38+
from typing_extensions import Protocol
3039
from .configuration import Configuration
3140
from .pipeline import AsyncPipeline
3241
from .pipeline.transport._base import PipelineClientBase
@@ -38,33 +47,88 @@
3847
AsyncRetryPolicy,
3948
)
4049

50+
51+
if TYPE_CHECKING: # Protocol and non-Protocol can't mix in Python 3.7
52+
53+
class _AsyncContextManagerCloseable(AsyncContextManager, Protocol):
54+
"""Defines a context manager that is closeable at the same time."""
55+
56+
async def close(self):
57+
...
58+
59+
4160
HTTPRequestType = TypeVar("HTTPRequestType")
42-
AsyncHTTPResponseType = TypeVar("AsyncHTTPResponseType")
61+
AsyncHTTPResponseType = TypeVar(
62+
"AsyncHTTPResponseType", bound="_AsyncContextManagerCloseable"
63+
)
4364

4465
_LOGGER = logging.getLogger(__name__)
4566

4667

47-
class _AsyncContextManager(collections.abc.Awaitable):
48-
def __init__(self, wrapped: collections.abc.Awaitable):
68+
class _Coroutine(Awaitable[AsyncHTTPResponseType]):
69+
"""Wrapper to get both context manager and awaitable in place.
70+
71+
Naming it "_Coroutine" because if you don't await it makes the error message easier:
72+
>>> result = client.send_request(request)
73+
>>> result.text()
74+
AttributeError: '_Coroutine' object has no attribute 'text'
75+
76+
Indeed, the message for calling a coroutine without waiting would be:
77+
AttributeError: 'coroutine' object has no attribute 'text'
78+
79+
This allows the dev to either use the "async with" syntax, or simply the object directly.
80+
It's also why "send_request" is not declared as async, since it couldn't be both easily.
81+
82+
"wrapped" must be an awaitable that returns an object that:
83+
- has an async "close()"
84+
- has an "__aexit__" method (IOW, is an async context manager)
85+
86+
This permits this code to work for both requests.
87+
88+
```python
89+
from azure.core import AsyncPipelineClient
90+
from azure.core.rest import HttpRequest
91+
92+
async def main():
93+
94+
request = HttpRequest("GET", "https://httpbin.org/user-agent")
95+
async with AsyncPipelineClient("https://httpbin.org/") as client:
96+
# Can be used directly
97+
result = await client.send_request(request)
98+
print(result.text())
99+
100+
# Can be used as an async context manager
101+
async with client.send_request(request) as result:
102+
print(result.text())
103+
```
104+
105+
:param wrapped: Must be an awaitable the returns an async context manager that supports async "close()"
106+
"""
107+
108+
def __init__(self, wrapped: Awaitable[AsyncHTTPResponseType]) -> None:
49109
super().__init__()
50-
self.wrapped = wrapped
51-
self.response = None
110+
self._wrapped = wrapped
111+
# If someone tries to use the object without awaiting, they will get a
112+
# AttributeError: '_Coroutine' object has no attribute 'text'
113+
self._response: AsyncHTTPResponseType = cast(AsyncHTTPResponseType, None)
52114

53-
def __await__(self):
54-
return self.wrapped.__await__()
115+
def __await__(self) -> Generator[Any, None, AsyncHTTPResponseType]:
116+
return self._wrapped.__await__()
55117

56-
async def __aenter__(self):
57-
self.response = await self
58-
return self.response
118+
async def __aenter__(self) -> AsyncHTTPResponseType:
119+
self._response = await self
120+
return self._response
59121

60-
async def __aexit__(self, *args):
61-
await self.response.__aexit__(*args)
122+
async def __aexit__(self, *args) -> None:
123+
await self._response.__aexit__(*args)
62124

63-
async def close(self):
64-
await self.response.close()
125+
async def close(self) -> None:
126+
await self._response.close()
65127

66128

67-
class AsyncPipelineClient(PipelineClientBase):
129+
class AsyncPipelineClient(
130+
PipelineClientBase, AsyncContextManager["AsyncPipelineClient"]
131+
):
68132
"""Service client core methods.
69133
70134
Builds an AsyncPipeline client.
@@ -212,4 +276,4 @@ def send_request(
212276
:rtype: ~azure.core.rest.AsyncHttpResponse
213277
"""
214278
wrapped = self._make_pipeline_call(request, stream=stream, **kwargs)
215-
return _AsyncContextManager(wrapped=wrapped)
279+
return _Coroutine(wrapped=wrapped)

sdk/core/azure-core/azure/core/async_paging.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
Tuple,
3434
Optional,
3535
Awaitable,
36+
Any,
3637
)
3738

3839
from .exceptions import AzureError
@@ -85,10 +86,10 @@ def __init__(
8586
self._extract_data = extract_data
8687
self.continuation_token = continuation_token
8788
self._did_a_call_already = False
88-
self._response = None
89-
self._current_page = None
89+
self._response: Optional[ResponseType] = None
90+
self._current_page: Optional[AsyncIterator[ReturnType]] = None
9091

91-
async def __anext__(self):
92+
async def __anext__(self) -> AsyncIterator[ReturnType]:
9293
if self.continuation_token is None and self._did_a_call_already:
9394
raise StopAsyncIteration("End of paging")
9495
try:
@@ -112,18 +113,16 @@ async def __anext__(self):
112113

113114

114115
class AsyncItemPaged(AsyncIterator[ReturnType]):
115-
def __init__(self, *args, **kwargs) -> None:
116+
def __init__(self, *args: Any, **kwargs: Any) -> None:
116117
"""Return an async iterator of items.
117118
118119
args and kwargs will be passed to the AsyncPageIterator constructor directly,
119120
except page_iterator_class
120121
"""
121122
self._args = args
122123
self._kwargs = kwargs
123-
self._page_iterator = (
124-
None
125-
) # type: Optional[AsyncIterator[AsyncIterator[ReturnType]]]
126-
self._page = None # type: Optional[AsyncIterator[ReturnType]]
124+
self._page_iterator: Optional[AsyncIterator[AsyncIterator[ReturnType]]] = None
125+
self._page: Optional[AsyncIterator[ReturnType]] = None
127126
self._page_iterator_class = self._kwargs.pop(
128127
"page_iterator_class", AsyncPageIterator
129128
)

sdk/core/azure-core/azure/core/configuration.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323
# IN THE SOFTWARE.
2424
#
2525
# --------------------------------------------------------------------------
26+
from typing import Union, Optional
2627

2728

28-
class Configuration(object):
29+
class Configuration:
2930
"""Provides the home for all of the configurable policies in the pipeline.
3031
3132
A new Configuration object provides no default policies and does not specify in what
@@ -88,16 +89,17 @@ def __init__(self, **kwargs):
8889
self.polling_interval = kwargs.get("polling_interval", 30)
8990

9091

91-
class ConnectionConfiguration(object):
92+
class ConnectionConfiguration:
9293
"""HTTP transport connection configuration settings.
9394
9495
Common properties that can be configured on all transports. Found in the
9596
Configuration object.
9697
97-
:keyword int connection_timeout: A single float in seconds for the connection timeout. Defaults to 300 seconds.
98-
:keyword int read_timeout: A single float in seconds for the read timeout. Defaults to 300 seconds.
99-
:keyword bool connection_verify: SSL certificate verification. Enabled by default. Set to False to disable,
98+
:keyword float connection_timeout: A single float in seconds for the connection timeout. Defaults to 300 seconds.
99+
:keyword float read_timeout: A single float in seconds for the read timeout. Defaults to 300 seconds.
100+
:keyword connection_verify: SSL certificate verification. Enabled by default. Set to False to disable,
100101
alternatively can be set to the path to a CA_BUNDLE file or directory with certificates of trusted CAs.
102+
:paramtype connection_verify: bool or str
101103
:keyword str connection_cert: Client-side certificates. You can specify a local cert to use as client side
102104
certificate, as a single file (containing the private key and the certificate) or as a tuple of both files' paths.
103105
:keyword int connection_data_block_size: The block size of data sent over the connection. Defaults to 4096 bytes.
@@ -112,9 +114,18 @@ class ConnectionConfiguration(object):
112114
:caption: Configuring transport connection settings.
113115
"""
114116

115-
def __init__(self, **kwargs):
116-
self.timeout = kwargs.pop("connection_timeout", 300)
117-
self.read_timeout = kwargs.pop("read_timeout", 300)
118-
self.verify = kwargs.pop("connection_verify", True)
119-
self.cert = kwargs.pop("connection_cert", None)
120-
self.data_block_size = kwargs.pop("connection_data_block_size", 4096)
117+
def __init__(
118+
self, # pylint: disable=unused-argument
119+
*,
120+
connection_timeout: float = 300,
121+
read_timeout: float = 300,
122+
connection_verify: Union[bool, str] = True,
123+
connection_cert: Optional[str] = None,
124+
connection_data_block_size: int = 4096,
125+
**kwargs
126+
) -> None:
127+
self.timeout = connection_timeout
128+
self.read_timeout = read_timeout
129+
self.verify = connection_verify
130+
self.cert = connection_cert
131+
self.data_block_size = connection_data_block_size

0 commit comments

Comments
 (0)
0