ZhengyangZhang commited on
Commit
5a5320a
·
verified ·
1 Parent(s): af0ec5a

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/asttokens/__init__.py +24 -0
  2. lib/python3.12/site-packages/asttokens/__pycache__/__init__.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/asttokens/__pycache__/astroid_compat.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/asttokens/__pycache__/asttokens.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/asttokens/__pycache__/line_numbers.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/asttokens/__pycache__/mark_tokens.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/asttokens/__pycache__/util.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/asttokens/__pycache__/version.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/asttokens/astroid_compat.py +18 -0
  10. lib/python3.12/site-packages/asttokens/asttokens.py +450 -0
  11. lib/python3.12/site-packages/asttokens/line_numbers.py +74 -0
  12. lib/python3.12/site-packages/asttokens/mark_tokens.py +469 -0
  13. lib/python3.12/site-packages/asttokens/py.typed +0 -0
  14. lib/python3.12/site-packages/asttokens/version.py +1 -0
  15. lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER +1 -0
  16. lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA +202 -0
  17. lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD +29 -0
  18. lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL +5 -0
  19. lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt +22 -0
  20. lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt +1 -0
  21. lib/python3.12/site-packages/httpcore/__init__.py +141 -0
  22. lib/python3.12/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/httpcore/__pycache__/_api.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/httpcore/__pycache__/_exceptions.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/httpcore/__pycache__/_models.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/httpcore/__pycache__/_ssl.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/httpcore/__pycache__/_trace.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/httpcore/_api.py +94 -0
  31. lib/python3.12/site-packages/httpcore/_async/__init__.py +39 -0
  32. lib/python3.12/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc +0 -0
  33. lib/python3.12/site-packages/httpcore/_async/__pycache__/connection.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/httpcore/_async/__pycache__/http2.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/httpcore/_async/connection.py +222 -0
  41. lib/python3.12/site-packages/httpcore/_async/connection_pool.py +420 -0
  42. lib/python3.12/site-packages/httpcore/_async/http11.py +379 -0
  43. lib/python3.12/site-packages/httpcore/_async/http2.py +592 -0
  44. lib/python3.12/site-packages/httpcore/_async/http_proxy.py +367 -0
  45. lib/python3.12/site-packages/httpcore/_async/interfaces.py +137 -0
  46. lib/python3.12/site-packages/httpcore/_async/socks_proxy.py +341 -0
  47. lib/python3.12/site-packages/httpcore/_backends/__init__.py +0 -0
  48. lib/python3.12/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc +0 -0
  50. lib/python3.12/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc +0 -0
lib/python3.12/site-packages/asttokens/__init__.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Grist Labs, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ This module enhances the Python AST tree with token and source code information, sufficent to
17
+ detect the source text of each AST node. This is helpful for tools that make source code
18
+ transformations.
19
+ """
20
+
21
+ from .line_numbers import LineNumbers
22
+ from .asttokens import ASTText, ASTTokens, supports_tokenless
23
+
24
+ __all__ = ['ASTText', 'ASTTokens', 'LineNumbers', 'supports_tokenless']
lib/python3.12/site-packages/asttokens/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (600 Bytes). View file
 
lib/python3.12/site-packages/asttokens/__pycache__/astroid_compat.cpython-312.pyc ADDED
Binary file (668 Bytes). View file
 
lib/python3.12/site-packages/asttokens/__pycache__/asttokens.cpython-312.pyc ADDED
Binary file (18 kB). View file
 
lib/python3.12/site-packages/asttokens/__pycache__/line_numbers.cpython-312.pyc ADDED
Binary file (3.65 kB). View file
 
lib/python3.12/site-packages/asttokens/__pycache__/mark_tokens.cpython-312.pyc ADDED
Binary file (20.1 kB). View file
 
lib/python3.12/site-packages/asttokens/__pycache__/util.cpython-312.pyc ADDED
Binary file (20.5 kB). View file
 
lib/python3.12/site-packages/asttokens/__pycache__/version.cpython-312.pyc ADDED
Binary file (213 Bytes). View file
 
lib/python3.12/site-packages/asttokens/astroid_compat.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ try:
2
+ from astroid import nodes as astroid_node_classes
3
+
4
+ # astroid_node_classes should be whichever module has the NodeNG class
5
+ from astroid.nodes import NodeNG
6
+ from astroid.nodes import BaseContainer
7
+ except Exception:
8
+ try:
9
+ from astroid import node_classes as astroid_node_classes
10
+ from astroid.node_classes import NodeNG
11
+ from astroid.node_classes import _BaseContainer as BaseContainer
12
+ except Exception: # pragma: no cover
13
+ astroid_node_classes = None
14
+ NodeNG = None
15
+ BaseContainer = None
16
+
17
+
18
+ __all__ = ["astroid_node_classes", "NodeNG", "BaseContainer"]
lib/python3.12/site-packages/asttokens/asttokens.py ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Grist Labs, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import abc
16
+ import ast
17
+ import bisect
18
+ import sys
19
+ import token
20
+ from ast import Module
21
+ from typing import Iterable, Iterator, List, Optional, Tuple, Any, cast, TYPE_CHECKING
22
+
23
+ from .line_numbers import LineNumbers
24
+ from .util import (
25
+ Token, match_token, is_non_coding_token, patched_generate_tokens, last_stmt,
26
+ annotate_fstring_nodes, generate_tokens, is_module, is_stmt
27
+ )
28
+
29
+ if TYPE_CHECKING: # pragma: no cover
30
+ from .util import AstNode, TokenInfo
31
+
32
+
33
+ class ASTTextBase(metaclass=abc.ABCMeta):
34
+ def __init__(self, source_text: str, filename: str) -> None:
35
+ self._filename = filename
36
+
37
+ # Decode source after parsing to let Python 2 handle coding declarations.
38
+ # (If the encoding was not utf-8 compatible, then even if it parses correctly,
39
+ # we'll fail with a unicode error here.)
40
+ source_text = str(source_text)
41
+
42
+ self._text = source_text
43
+ self._line_numbers = LineNumbers(source_text)
44
+
45
+ @abc.abstractmethod
46
+ def get_text_positions(self, node, padded):
47
+ # type: (AstNode, bool) -> Tuple[Tuple[int, int], Tuple[int, int]]
48
+ """
49
+ Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
50
+ If the positions can't be determined, or the nodes don't correspond to any particular text,
51
+ returns ``(1, 0)`` for both.
52
+
53
+ ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
54
+ This means that if ``padded`` is True, the start position will be adjusted to include
55
+ leading whitespace if ``node`` is a multiline statement.
56
+ """
57
+ raise NotImplementedError # pragma: no cover
58
+
59
+ def get_text_range(self, node, padded=True):
60
+ # type: (AstNode, bool) -> Tuple[int, int]
61
+ """
62
+ Returns the (startpos, endpos) positions in source text corresponding to the given node.
63
+ Returns (0, 0) for nodes (like `Load`) that don't correspond to any particular text.
64
+
65
+ See ``get_text_positions()`` for details on the ``padded`` argument.
66
+ """
67
+ start, end = self.get_text_positions(node, padded)
68
+ return (
69
+ self._line_numbers.line_to_offset(*start),
70
+ self._line_numbers.line_to_offset(*end),
71
+ )
72
+
73
+ def get_text(self, node, padded=True):
74
+ # type: (AstNode, bool) -> str
75
+ """
76
+ Returns the text corresponding to the given node.
77
+ Returns '' for nodes (like `Load`) that don't correspond to any particular text.
78
+
79
+ See ``get_text_positions()`` for details on the ``padded`` argument.
80
+ """
81
+ start, end = self.get_text_range(node, padded)
82
+ return self._text[start: end]
83
+
84
+
85
+ class ASTTokens(ASTTextBase):
86
+ """
87
+ ASTTokens maintains the text of Python code in several forms: as a string, as line numbers, and
88
+ as tokens, and is used to mark and access token and position information.
89
+
90
+ ``source_text`` must be a unicode or UTF8-encoded string. If you pass in UTF8 bytes, remember
91
+ that all offsets you'll get are to the unicode text, which is available as the ``.text``
92
+ property.
93
+
94
+ If ``parse`` is set, the ``source_text`` will be parsed with ``ast.parse()``, and the resulting
95
+ tree marked with token info and made available as the ``.tree`` property.
96
+
97
+ If ``tree`` is given, it will be marked and made available as the ``.tree`` property. In
98
+ addition to the trees produced by the ``ast`` module, ASTTokens will also mark trees produced
99
+ using ``astroid`` library <https://www.astroid.org>.
100
+
101
+ If only ``source_text`` is given, you may use ``.mark_tokens(tree)`` to mark the nodes of an AST
102
+ tree created separately.
103
+ """
104
+
105
+ def __init__(self, source_text, parse=False, tree=None, filename='<unknown>', tokens=None):
106
+ # type: (Any, bool, Optional[Module], str, Optional[Iterable[TokenInfo]]) -> None
107
+ super(ASTTokens, self).__init__(source_text, filename)
108
+
109
+ self._tree = ast.parse(source_text, filename) if parse else tree
110
+
111
+ # Tokenize the code.
112
+ if tokens is None:
113
+ tokens = generate_tokens(self._text)
114
+ self._tokens = list(self._translate_tokens(tokens))
115
+
116
+ # Extract the start positions of all tokens, so that we can quickly map positions to tokens.
117
+ self._token_offsets = [tok.startpos for tok in self._tokens]
118
+
119
+ if self._tree:
120
+ self.mark_tokens(self._tree)
121
+
122
+ def mark_tokens(self, root_node):
123
+ # type: (Module) -> None
124
+ """
125
+ Given the root of the AST or Astroid tree produced from source_text, visits all nodes marking
126
+ them with token and position information by adding ``.first_token`` and
127
+ ``.last_token`` attributes. This is done automatically in the constructor when ``parse`` or
128
+ ``tree`` arguments are set, but may be used manually with a separate AST or Astroid tree.
129
+ """
130
+ # The hard work of this class is done by MarkTokens
131
+ from .mark_tokens import MarkTokens # to avoid import loops
132
+ MarkTokens(self).visit_tree(root_node)
133
+
134
+ def _translate_tokens(self, original_tokens):
135
+ # type: (Iterable[TokenInfo]) -> Iterator[Token]
136
+ """
137
+ Translates the given standard library tokens into our own representation.
138
+ """
139
+ for index, tok in enumerate(patched_generate_tokens(original_tokens)):
140
+ tok_type, tok_str, start, end, line = tok
141
+ yield Token(tok_type, tok_str, start, end, line, index,
142
+ self._line_numbers.line_to_offset(start[0], start[1]),
143
+ self._line_numbers.line_to_offset(end[0], end[1]))
144
+
145
+ @property
146
+ def text(self):
147
+ # type: () -> str
148
+ """The source code passed into the constructor."""
149
+ return self._text
150
+
151
+ @property
152
+ def tokens(self):
153
+ # type: () -> List[Token]
154
+ """The list of tokens corresponding to the source code from the constructor."""
155
+ return self._tokens
156
+
157
+ @property
158
+ def tree(self):
159
+ # type: () -> Optional[Module]
160
+ """The root of the AST tree passed into the constructor or parsed from the source code."""
161
+ return self._tree
162
+
163
+ @property
164
+ def filename(self):
165
+ # type: () -> str
166
+ """The filename that was parsed"""
167
+ return self._filename
168
+
169
+ def get_token_from_offset(self, offset):
170
+ # type: (int) -> Token
171
+ """
172
+ Returns the token containing the given character offset (0-based position in source text),
173
+ or the preceeding token if the position is between tokens.
174
+ """
175
+ return self._tokens[bisect.bisect(self._token_offsets, offset) - 1]
176
+
177
+ def get_token(self, lineno, col_offset):
178
+ # type: (int, int) -> Token
179
+ """
180
+ Returns the token containing the given (lineno, col_offset) position, or the preceeding token
181
+ if the position is between tokens.
182
+ """
183
+ # TODO: add test for multibyte unicode. We need to translate offsets from ast module (which
184
+ # are in utf8) to offsets into the unicode text. tokenize module seems to use unicode offsets
185
+ # but isn't explicit.
186
+ return self.get_token_from_offset(self._line_numbers.line_to_offset(lineno, col_offset))
187
+
188
+ def get_token_from_utf8(self, lineno, col_offset):
189
+ # type: (int, int) -> Token
190
+ """
191
+ Same as get_token(), but interprets col_offset as a UTF8 offset, which is what `ast` uses.
192
+ """
193
+ return self.get_token(lineno, self._line_numbers.from_utf8_col(lineno, col_offset))
194
+
195
+ def next_token(self, tok, include_extra=False):
196
+ # type: (Token, bool) -> Token
197
+ """
198
+ Returns the next token after the given one. If include_extra is True, includes non-coding
199
+ tokens from the tokenize module, such as NL and COMMENT.
200
+ """
201
+ i = tok.index + 1
202
+ if not include_extra:
203
+ while is_non_coding_token(self._tokens[i].type):
204
+ i += 1
205
+ return self._tokens[i]
206
+
207
+ def prev_token(self, tok, include_extra=False):
208
+ # type: (Token, bool) -> Token
209
+ """
210
+ Returns the previous token before the given one. If include_extra is True, includes non-coding
211
+ tokens from the tokenize module, such as NL and COMMENT.
212
+ """
213
+ i = tok.index - 1
214
+ if not include_extra:
215
+ while is_non_coding_token(self._tokens[i].type):
216
+ i -= 1
217
+ return self._tokens[i]
218
+
219
+ def find_token(self, start_token, tok_type, tok_str=None, reverse=False):
220
+ # type: (Token, int, Optional[str], bool) -> Token
221
+ """
222
+ Looks for the first token, starting at start_token, that matches tok_type and, if given, the
223
+ token string. Searches backwards if reverse is True. Returns ENDMARKER token if not found (you
224
+ can check it with `token.ISEOF(t.type)`).
225
+ """
226
+ t = start_token
227
+ advance = self.prev_token if reverse else self.next_token
228
+ while not match_token(t, tok_type, tok_str) and not token.ISEOF(t.type):
229
+ t = advance(t, include_extra=True)
230
+ return t
231
+
232
+ def token_range(self,
233
+ first_token, # type: Token
234
+ last_token, # type: Token
235
+ include_extra=False, # type: bool
236
+ ):
237
+ # type: (...) -> Iterator[Token]
238
+ """
239
+ Yields all tokens in order from first_token through and including last_token. If
240
+ include_extra is True, includes non-coding tokens such as tokenize.NL and .COMMENT.
241
+ """
242
+ for i in range(first_token.index, last_token.index + 1):
243
+ if include_extra or not is_non_coding_token(self._tokens[i].type):
244
+ yield self._tokens[i]
245
+
246
+ def get_tokens(self, node, include_extra=False):
247
+ # type: (AstNode, bool) -> Iterator[Token]
248
+ """
249
+ Yields all tokens making up the given node. If include_extra is True, includes non-coding
250
+ tokens such as tokenize.NL and .COMMENT.
251
+ """
252
+ return self.token_range(node.first_token, node.last_token, include_extra=include_extra)
253
+
254
+ def get_text_positions(self, node, padded):
255
+ # type: (AstNode, bool) -> Tuple[Tuple[int, int], Tuple[int, int]]
256
+ """
257
+ Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
258
+ If the positions can't be determined, or the nodes don't correspond to any particular text,
259
+ returns ``(1, 0)`` for both.
260
+
261
+ ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
262
+ This means that if ``padded`` is True, the start position will be adjusted to include
263
+ leading whitespace if ``node`` is a multiline statement.
264
+ """
265
+ if not hasattr(node, 'first_token'):
266
+ return (1, 0), (1, 0)
267
+
268
+ start = node.first_token.start
269
+ end = node.last_token.end
270
+ if padded and any(match_token(t, token.NEWLINE) for t in self.get_tokens(node)):
271
+ # Set col_offset to 0 to include leading indentation for multiline statements.
272
+ start = (start[0], 0)
273
+
274
+ return start, end
275
+
276
+
277
+ class ASTText(ASTTextBase):
278
+ """
279
+ Supports the same ``get_text*`` methods as ``ASTTokens``,
280
+ but uses the AST to determine the text positions instead of tokens.
281
+ This is faster than ``ASTTokens`` as it requires less setup work.
282
+
283
+ It also (sometimes) supports nodes inside f-strings, which ``ASTTokens`` doesn't.
284
+
285
+ Some node types and/or Python versions are not supported.
286
+ In these cases the ``get_text*`` methods will fall back to using ``ASTTokens``
287
+ which incurs the usual setup cost the first time.
288
+ If you want to avoid this, check ``supports_tokenless(node)`` before calling ``get_text*`` methods.
289
+ """
290
+ def __init__(self, source_text, tree=None, filename='<unknown>'):
291
+ # type: (Any, Optional[Module], str) -> None
292
+ super(ASTText, self).__init__(source_text, filename)
293
+
294
+ self._tree = tree
295
+ if self._tree is not None:
296
+ annotate_fstring_nodes(self._tree)
297
+
298
+ self._asttokens = None # type: Optional[ASTTokens]
299
+
300
+ @property
301
+ def tree(self):
302
+ # type: () -> Module
303
+ if self._tree is None:
304
+ self._tree = ast.parse(self._text, self._filename)
305
+ annotate_fstring_nodes(self._tree)
306
+ return self._tree
307
+
308
+ @property
309
+ def asttokens(self):
310
+ # type: () -> ASTTokens
311
+ if self._asttokens is None:
312
+ self._asttokens = ASTTokens(
313
+ self._text,
314
+ tree=self.tree,
315
+ filename=self._filename,
316
+ )
317
+ return self._asttokens
318
+
319
+ def _get_text_positions_tokenless(self, node, padded):
320
+ # type: (AstNode, bool) -> Tuple[Tuple[int, int], Tuple[int, int]]
321
+ """
322
+ Version of ``get_text_positions()`` that doesn't use tokens.
323
+ """
324
+ if is_module(node):
325
+ # Modules don't have position info, so just return the range of the whole text.
326
+ # The token-using method does something different, but its behavior seems weird and inconsistent.
327
+ # For example, in a file with only comments, it only returns the first line.
328
+ # It's hard to imagine a case when this matters.
329
+ return (1, 0), self._line_numbers.offset_to_line(len(self._text))
330
+
331
+ if getattr(node, 'lineno', None) is None:
332
+ return (1, 0), (1, 0)
333
+
334
+ assert node # tell mypy that node is not None, which we allowed up to here for compatibility
335
+
336
+ decorators = getattr(node, 'decorator_list', [])
337
+ if not decorators:
338
+ # Astroid uses node.decorators.nodes instead of node.decorator_list.
339
+ decorators_node = getattr(node, 'decorators', None)
340
+ decorators = getattr(decorators_node, 'nodes', [])
341
+ if decorators:
342
+ # Function/Class definition nodes are marked by AST as starting at def/class,
343
+ # not the first decorator. This doesn't match the token-using behavior,
344
+ # or inspect.getsource(), and just seems weird.
345
+ start_node = decorators[0]
346
+ else:
347
+ start_node = node
348
+
349
+ start_lineno = start_node.lineno
350
+ end_node = last_stmt(node)
351
+
352
+ # Include leading indentation for multiline statements.
353
+ # This doesn't mean simple statements that happen to be on multiple lines,
354
+ # but compound statements where inner indentation matters.
355
+ # So we don't just compare node.lineno and node.end_lineno,
356
+ # we check for a contained statement starting on a different line.
357
+ if padded and (
358
+ start_lineno != end_node.lineno
359
+ or (
360
+ # Astroid docstrings aren't treated as separate statements.
361
+ # So to handle function/class definitions with a docstring but no other body,
362
+ # we just check that the node is a statement with a docstring
363
+ # and spanning multiple lines in the simple, literal sense.
364
+ start_lineno != node.end_lineno
365
+ and getattr(node, "doc_node", None)
366
+ and is_stmt(node)
367
+ )
368
+ ):
369
+ start_col_offset = 0
370
+ else:
371
+ start_col_offset = self._line_numbers.from_utf8_col(start_lineno, start_node.col_offset)
372
+
373
+ start = (start_lineno, start_col_offset)
374
+
375
+ # To match the token-using behaviour, we exclude trailing semicolons and comments.
376
+ # This means that for blocks containing multiple statements, we have to use the last one
377
+ # instead of the actual node for end_lineno and end_col_offset.
378
+ end_lineno = cast(int, end_node.end_lineno)
379
+ end_col_offset = cast(int, end_node.end_col_offset)
380
+ end_col_offset = self._line_numbers.from_utf8_col(end_lineno, end_col_offset)
381
+ end = (end_lineno, end_col_offset)
382
+
383
+ return start, end
384
+
385
+ def get_text_positions(self, node, padded):
386
+ # type: (AstNode, bool) -> Tuple[Tuple[int, int], Tuple[int, int]]
387
+ """
388
+ Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
389
+ If the positions can't be determined, or the nodes don't correspond to any particular text,
390
+ returns ``(1, 0)`` for both.
391
+
392
+ ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
393
+ This means that if ``padded`` is True, the start position will be adjusted to include
394
+ leading whitespace if ``node`` is a multiline statement.
395
+ """
396
+ if getattr(node, "_broken_positions", None):
397
+ # This node was marked in util.annotate_fstring_nodes as having untrustworthy lineno/col_offset.
398
+ return (1, 0), (1, 0)
399
+
400
+ if supports_tokenless(node):
401
+ return self._get_text_positions_tokenless(node, padded)
402
+
403
+ return self.asttokens.get_text_positions(node, padded)
404
+
405
+
406
+ # Node types that _get_text_positions_tokenless doesn't support.
407
+ # These initial values are missing lineno.
408
+ _unsupported_tokenless_types = ("arguments", "Arguments", "withitem") # type: Tuple[str, ...]
409
+ if sys.version_info[:2] == (3, 8):
410
+ # _get_text_positions_tokenless works incorrectly for these types due to bugs in Python 3.8.
411
+ _unsupported_tokenless_types += ("arg", "Starred")
412
+ # no lineno in 3.8
413
+ _unsupported_tokenless_types += ("Slice", "ExtSlice", "Index", "keyword")
414
+
415
+
416
+ def supports_tokenless(node=None):
417
+ # type: (Any) -> bool
418
+ """
419
+ Returns True if the Python version and the node (if given) are supported by
420
+ the ``get_text*`` methods of ``ASTText`` without falling back to ``ASTTokens``.
421
+ See ``ASTText`` for why this matters.
422
+
423
+ The following cases are not supported:
424
+
425
+ - PyPy
426
+ - ``ast.arguments`` / ``astroid.Arguments``
427
+ - ``ast.withitem``
428
+ - ``astroid.Comprehension``
429
+ - ``astroid.AssignName`` inside ``astroid.Arguments`` or ``astroid.ExceptHandler``
430
+ - The following nodes in Python 3.8 only:
431
+ - ``ast.arg``
432
+ - ``ast.Starred``
433
+ - ``ast.Slice``
434
+ - ``ast.ExtSlice``
435
+ - ``ast.Index``
436
+ - ``ast.keyword``
437
+ """
438
+ return (
439
+ type(node).__name__ not in _unsupported_tokenless_types
440
+ and not (
441
+ # astroid nodes
442
+ not isinstance(node, ast.AST) and node is not None and (
443
+ (
444
+ type(node).__name__ == "AssignName"
445
+ and type(node.parent).__name__ in ("Arguments", "ExceptHandler")
446
+ )
447
+ )
448
+ )
449
+ and 'pypy' not in sys.version.lower()
450
+ )
lib/python3.12/site-packages/asttokens/line_numbers.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Grist Labs, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import bisect
16
+ import re
17
+ from typing import Dict, List, Tuple
18
+
19
+ _line_start_re = re.compile(r'^', re.M)
20
+
21
+ class LineNumbers:
22
+ """
23
+ Class to convert between character offsets in a text string, and pairs (line, column) of 1-based
24
+ line and 0-based column numbers, as used by tokens and AST nodes.
25
+
26
+ This class expects unicode for input and stores positions in unicode. But it supports
27
+ translating to and from utf8 offsets, which are used by ast parsing.
28
+ """
29
+ def __init__(self, text):
30
+ # type: (str) -> None
31
+ # A list of character offsets of each line's first character.
32
+ self._line_offsets = [m.start(0) for m in _line_start_re.finditer(text)]
33
+ self._text = text
34
+ self._text_len = len(text)
35
+ self._utf8_offset_cache = {} # type: Dict[int, List[int]] # maps line num to list of char offset for each byte in line
36
+
37
+ def from_utf8_col(self, line, utf8_column):
38
+ # type: (int, int) -> int
39
+ """
40
+ Given a 1-based line number and 0-based utf8 column, returns a 0-based unicode column.
41
+ """
42
+ offsets = self._utf8_offset_cache.get(line)
43
+ if offsets is None:
44
+ end_offset = self._line_offsets[line] if line < len(self._line_offsets) else self._text_len
45
+ line_text = self._text[self._line_offsets[line - 1] : end_offset]
46
+
47
+ offsets = [i for i,c in enumerate(line_text) for byte in c.encode('utf8')]
48
+ offsets.append(len(line_text))
49
+ self._utf8_offset_cache[line] = offsets
50
+
51
+ return offsets[max(0, min(len(offsets)-1, utf8_column))]
52
+
53
+ def line_to_offset(self, line, column):
54
+ # type: (int, int) -> int
55
+ """
56
+ Converts 1-based line number and 0-based column to 0-based character offset into text.
57
+ """
58
+ line -= 1
59
+ if line >= len(self._line_offsets):
60
+ return self._text_len
61
+ elif line < 0:
62
+ return 0
63
+ else:
64
+ return min(self._line_offsets[line] + max(0, column), self._text_len)
65
+
66
+ def offset_to_line(self, offset):
67
+ # type: (int) -> Tuple[int, int]
68
+ """
69
+ Converts 0-based character offset to pair (line, col) of 1-based line and 0-based column
70
+ numbers.
71
+ """
72
+ offset = max(0, min(self._text_len, offset))
73
+ line_index = bisect.bisect_right(self._line_offsets, offset) - 1
74
+ return (line_index + 1, offset - self._line_offsets[line_index])
lib/python3.12/site-packages/asttokens/mark_tokens.py ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Grist Labs, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import ast
16
+ import numbers
17
+ import sys
18
+ import token
19
+ from ast import Module
20
+ from typing import Callable, List, Union, cast, Optional, Tuple, TYPE_CHECKING
21
+
22
+ from . import util
23
+ from .asttokens import ASTTokens
24
+ from .astroid_compat import astroid_node_classes as nc, BaseContainer as AstroidBaseContainer
25
+
26
+ if TYPE_CHECKING:
27
+ from .util import AstNode
28
+
29
+
30
+ # Mapping of matching braces. To find a token here, look up token[:2].
31
+ _matching_pairs_left = {
32
+ (token.OP, '('): (token.OP, ')'),
33
+ (token.OP, '['): (token.OP, ']'),
34
+ (token.OP, '{'): (token.OP, '}'),
35
+ }
36
+
37
+ _matching_pairs_right = {
38
+ (token.OP, ')'): (token.OP, '('),
39
+ (token.OP, ']'): (token.OP, '['),
40
+ (token.OP, '}'): (token.OP, '{'),
41
+ }
42
+
43
+
44
+ class MarkTokens:
45
+ """
46
+ Helper that visits all nodes in the AST tree and assigns .first_token and .last_token attributes
47
+ to each of them. This is the heart of the token-marking logic.
48
+ """
49
+ def __init__(self, code):
50
+ # type: (ASTTokens) -> None
51
+ self._code = code
52
+ self._methods = util.NodeMethods()
53
+ self._iter_children = None # type: Optional[Callable]
54
+
55
+ def visit_tree(self, node):
56
+ # type: (Module) -> None
57
+ self._iter_children = util.iter_children_func(node)
58
+ util.visit_tree(node, self._visit_before_children, self._visit_after_children)
59
+
60
+ def _visit_before_children(self, node, parent_token):
61
+ # type: (AstNode, Optional[util.Token]) -> Tuple[Optional[util.Token], Optional[util.Token]]
62
+ col = getattr(node, 'col_offset', None)
63
+ token = self._code.get_token_from_utf8(node.lineno, col) if col is not None else None
64
+
65
+ if not token and util.is_module(node):
66
+ # We'll assume that a Module node starts at the start of the source code.
67
+ token = self._code.get_token(1, 0)
68
+
69
+ # Use our own token, or our parent's if we don't have one, to pass to child calls as
70
+ # parent_token argument. The second value becomes the token argument of _visit_after_children.
71
+ return (token or parent_token, token)
72
+
73
+ def _visit_after_children(self, node, parent_token, token):
74
+ # type: (AstNode, Optional[util.Token], Optional[util.Token]) -> None
75
+ # This processes the node generically first, after all children have been processed.
76
+
77
+ # Get the first and last tokens that belong to children. Note how this doesn't assume that we
78
+ # iterate through children in order that corresponds to occurrence in source code. This
79
+ # assumption can fail (e.g. with return annotations).
80
+ first = token
81
+ last = None
82
+ for child in cast(Callable, self._iter_children)(node):
83
+ # astroid slices have especially wrong positions, we don't want them to corrupt their parents.
84
+ if util.is_empty_astroid_slice(child):
85
+ continue
86
+ if not first or child.first_token.index < first.index:
87
+ first = child.first_token
88
+ if not last or child.last_token.index > last.index:
89
+ last = child.last_token
90
+
91
+ # If we don't have a first token from _visit_before_children, and there were no children, then
92
+ # use the parent's token as the first token.
93
+ first = first or parent_token
94
+
95
+ # If no children, set last token to the first one.
96
+ last = last or first
97
+
98
+ # Statements continue to before NEWLINE. This helps cover a few different cases at once.
99
+ if util.is_stmt(node):
100
+ last = self._find_last_in_stmt(cast(util.Token, last))
101
+
102
+ # Capture any unmatched brackets.
103
+ first, last = self._expand_to_matching_pairs(cast(util.Token, first), cast(util.Token, last), node)
104
+
105
+ # Give a chance to node-specific methods to adjust.
106
+ nfirst, nlast = self._methods.get(self, node.__class__)(node, first, last)
107
+
108
+ if (nfirst, nlast) != (first, last):
109
+ # If anything changed, expand again to capture any unmatched brackets.
110
+ nfirst, nlast = self._expand_to_matching_pairs(nfirst, nlast, node)
111
+
112
+ node.first_token = nfirst
113
+ node.last_token = nlast
114
+
115
+ def _find_last_in_stmt(self, start_token):
116
+ # type: (util.Token) -> util.Token
117
+ t = start_token
118
+ while (not util.match_token(t, token.NEWLINE) and
119
+ not util.match_token(t, token.OP, ';') and
120
+ not token.ISEOF(t.type)):
121
+ t = self._code.next_token(t, include_extra=True)
122
+ return self._code.prev_token(t)
123
+
124
+ def _expand_to_matching_pairs(self, first_token, last_token, node):
125
+ # type: (util.Token, util.Token, AstNode) -> Tuple[util.Token, util.Token]
126
+ """
127
+ Scan tokens in [first_token, last_token] range that are between node's children, and for any
128
+ unmatched brackets, adjust first/last tokens to include the closing pair.
129
+ """
130
+ # We look for opening parens/braces among non-child tokens (i.e. tokens between our actual
131
+ # child nodes). If we find any closing ones, we match them to the opens.
132
+ to_match_right = [] # type: List[Tuple[int, str]]
133
+ to_match_left = []
134
+ for tok in self._code.token_range(first_token, last_token):
135
+ tok_info = tok[:2]
136
+ if to_match_right and tok_info == to_match_right[-1]:
137
+ to_match_right.pop()
138
+ elif tok_info in _matching_pairs_left:
139
+ to_match_right.append(_matching_pairs_left[tok_info])
140
+ elif tok_info in _matching_pairs_right:
141
+ to_match_left.append(_matching_pairs_right[tok_info])
142
+
143
+ # Once done, extend `last_token` to match any unclosed parens/braces.
144
+ for match in reversed(to_match_right):
145
+ last = self._code.next_token(last_token)
146
+ # Allow for trailing commas or colons (allowed in subscripts) before the closing delimiter
147
+ while any(util.match_token(last, token.OP, x) for x in (',', ':')):
148
+ last = self._code.next_token(last)
149
+ # Now check for the actual closing delimiter.
150
+ if util.match_token(last, *match):
151
+ last_token = last
152
+
153
+ # And extend `first_token` to match any unclosed opening parens/braces.
154
+ for match in to_match_left:
155
+ first = self._code.prev_token(first_token)
156
+ if util.match_token(first, *match):
157
+ first_token = first
158
+
159
+ return (first_token, last_token)
160
+
161
+ #----------------------------------------------------------------------
162
+ # Node visitors. Each takes a preliminary first and last tokens, and returns the adjusted pair
163
+ # that will actually be assigned.
164
+
165
+ def visit_default(self, node, first_token, last_token):
166
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
167
+ # pylint: disable=no-self-use
168
+ # By default, we don't need to adjust the token we computed earlier.
169
+ return (first_token, last_token)
170
+
171
+ def handle_comp(self, open_brace, node, first_token, last_token):
172
+ # type: (str, AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
173
+ # For list/set/dict comprehensions, we only get the token of the first child, so adjust it to
174
+ # include the opening brace (the closing brace will be matched automatically).
175
+ before = self._code.prev_token(first_token)
176
+ util.expect_token(before, token.OP, open_brace)
177
+ return (before, last_token)
178
+
179
+ def visit_comprehension(self,
180
+ node, # type: AstNode
181
+ first_token, # type: util.Token
182
+ last_token, # type: util.Token
183
+ ):
184
+ # type: (...) -> Tuple[util.Token, util.Token]
185
+ # The 'comprehension' node starts with 'for' but we only get first child; we search backwards
186
+ # to find the 'for' keyword.
187
+ first = self._code.find_token(first_token, token.NAME, 'for', reverse=True)
188
+ return (first, last_token)
189
+
190
+ def visit_if(self, node, first_token, last_token):
191
+ # type: (util.Token, util.Token, util.Token) -> Tuple[util.Token, util.Token]
192
+ while first_token.string not in ('if', 'elif'):
193
+ first_token = self._code.prev_token(first_token)
194
+ return first_token, last_token
195
+
196
+ def handle_attr(self, node, first_token, last_token):
197
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
198
+ # Attribute node has ".attr" (2 tokens) after the last child.
199
+ dot = self._code.find_token(last_token, token.OP, '.')
200
+ name = self._code.next_token(dot)
201
+ util.expect_token(name, token.NAME)
202
+ return (first_token, name)
203
+
204
+ visit_attribute = handle_attr
205
+ visit_assignattr = handle_attr
206
+ visit_delattr = handle_attr
207
+
208
+ def handle_def(self, node, first_token, last_token):
209
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
210
+ # With astroid, nodes that start with a doc-string can have an empty body, in which case we
211
+ # need to adjust the last token to include the doc string.
212
+ if not node.body and (getattr(node, 'doc_node', None) or getattr(node, 'doc', None)): # type: ignore[union-attr]
213
+ last_token = self._code.find_token(last_token, token.STRING)
214
+
215
+ # Include @ from decorator
216
+ if first_token.index > 0:
217
+ prev = self._code.prev_token(first_token)
218
+ if util.match_token(prev, token.OP, '@'):
219
+ first_token = prev
220
+ return (first_token, last_token)
221
+
222
+ visit_classdef = handle_def
223
+ visit_functiondef = handle_def
224
+
225
+ def handle_following_brackets(self, node, last_token, opening_bracket):
226
+ # type: (AstNode, util.Token, str) -> util.Token
227
+ # This is for calls and subscripts, which have a pair of brackets
228
+ # at the end which may contain no nodes, e.g. foo() or bar[:].
229
+ # We look for the opening bracket and then let the matching pair be found automatically
230
+ # Remember that last_token is at the end of all children,
231
+ # so we are not worried about encountering a bracket that belongs to a child.
232
+ first_child = next(cast(Callable, self._iter_children)(node))
233
+ call_start = self._code.find_token(first_child.last_token, token.OP, opening_bracket)
234
+ if call_start.index > last_token.index:
235
+ last_token = call_start
236
+ return last_token
237
+
238
+ def visit_call(self, node, first_token, last_token):
239
+ # type: (util.Token, util.Token, util.Token) -> Tuple[util.Token, util.Token]
240
+ last_token = self.handle_following_brackets(node, last_token, '(')
241
+
242
+ # Handling a python bug with decorators with empty parens, e.g.
243
+ # @deco()
244
+ # def ...
245
+ if util.match_token(first_token, token.OP, '@'):
246
+ first_token = self._code.next_token(first_token)
247
+ return (first_token, last_token)
248
+
249
+ def visit_matchclass(self, node, first_token, last_token):
250
+ # type: (util.Token, util.Token, util.Token) -> Tuple[util.Token, util.Token]
251
+ last_token = self.handle_following_brackets(node, last_token, '(')
252
+ return (first_token, last_token)
253
+
254
+ def visit_subscript(self,
255
+ node, # type: AstNode
256
+ first_token, # type: util.Token
257
+ last_token, # type: util.Token
258
+ ):
259
+ # type: (...) -> Tuple[util.Token, util.Token]
260
+ last_token = self.handle_following_brackets(node, last_token, '[')
261
+ return (first_token, last_token)
262
+
263
+ def visit_slice(self, node, first_token, last_token):
264
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
265
+ # consume `:` tokens to the left and right. In Python 3.9, Slice nodes are
266
+ # given a col_offset, (and end_col_offset), so this will always start inside
267
+ # the slice, even if it is the empty slice. However, in 3.8 and below, this
268
+ # will only expand to the full slice if the slice contains a node with a
269
+ # col_offset. So x[:] will only get the correct tokens in 3.9, but x[1:] and
270
+ # x[:1] will even on earlier versions of Python.
271
+ while True:
272
+ prev = self._code.prev_token(first_token)
273
+ if prev.string != ':':
274
+ break
275
+ first_token = prev
276
+ while True:
277
+ next_ = self._code.next_token(last_token)
278
+ if next_.string != ':':
279
+ break
280
+ last_token = next_
281
+ return (first_token, last_token)
282
+
283
+ def handle_bare_tuple(self, node, first_token, last_token):
284
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
285
+ # A bare tuple doesn't include parens; if there is a trailing comma, make it part of the tuple.
286
+ maybe_comma = self._code.next_token(last_token)
287
+ if util.match_token(maybe_comma, token.OP, ','):
288
+ last_token = maybe_comma
289
+ return (first_token, last_token)
290
+
291
+ # In Python3.8 parsed tuples include parentheses when present.
292
+ def handle_tuple_nonempty(self, node, first_token, last_token):
293
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
294
+ assert isinstance(node, ast.Tuple) or isinstance(node, AstroidBaseContainer)
295
+ # It's a bare tuple if the first token belongs to the first child. The first child may
296
+ # include extraneous parentheses (which don't create new nodes), so account for those too.
297
+ child = node.elts[0]
298
+ if TYPE_CHECKING:
299
+ child = cast(AstNode, child)
300
+ child_first, child_last = self._gobble_parens(child.first_token, child.last_token, True)
301
+ if first_token == child_first:
302
+ return self.handle_bare_tuple(node, first_token, last_token)
303
+ return (first_token, last_token)
304
+
305
+ def visit_tuple(self, node, first_token, last_token):
306
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
307
+ assert isinstance(node, ast.Tuple) or isinstance(node, AstroidBaseContainer)
308
+ if not node.elts:
309
+ # An empty tuple is just "()", and we need no further info.
310
+ return (first_token, last_token)
311
+ return self.handle_tuple_nonempty(node, first_token, last_token)
312
+
313
+ def _gobble_parens(self, first_token, last_token, include_all=False):
314
+ # type: (util.Token, util.Token, bool) -> Tuple[util.Token, util.Token]
315
+ # Expands a range of tokens to include one or all pairs of surrounding parentheses, and
316
+ # returns (first, last) tokens that include these parens.
317
+ while first_token.index > 0:
318
+ prev = self._code.prev_token(first_token)
319
+ next = self._code.next_token(last_token)
320
+ if util.match_token(prev, token.OP, '(') and util.match_token(next, token.OP, ')'):
321
+ first_token, last_token = prev, next
322
+ if include_all:
323
+ continue
324
+ break
325
+ return (first_token, last_token)
326
+
327
+ def visit_str(self, node, first_token, last_token):
328
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
329
+ return self.handle_str(first_token, last_token)
330
+
331
+ def visit_joinedstr(self,
332
+ node, # type: AstNode
333
+ first_token, # type: util.Token
334
+ last_token, # type: util.Token
335
+ ):
336
+ # type: (...) -> Tuple[util.Token, util.Token]
337
+ if sys.version_info < (3, 12):
338
+ # Older versions don't tokenize the contents of f-strings
339
+ return self.handle_str(first_token, last_token)
340
+
341
+ last = first_token
342
+ while True:
343
+ if util.match_token(last, getattr(token, "FSTRING_START")):
344
+ # Python 3.12+ has tokens for the start (e.g. `f"`) and end (`"`)
345
+ # of the f-string. We can't just look for the next FSTRING_END
346
+ # because f-strings can be nested, e.g. f"{f'{x}'}", so we need
347
+ # to treat this like matching balanced parentheses.
348
+ count = 1
349
+ while count > 0:
350
+ last = self._code.next_token(last)
351
+ # mypy complains about token.FSTRING_START and token.FSTRING_END.
352
+ if util.match_token(last, getattr(token, "FSTRING_START")):
353
+ count += 1
354
+ elif util.match_token(last, getattr(token, "FSTRING_END")):
355
+ count -= 1
356
+ last_token = last
357
+ last = self._code.next_token(last_token)
358
+ elif util.match_token(last, token.STRING):
359
+ # Similar to handle_str, we also need to handle adjacent strings.
360
+ last_token = last
361
+ last = self._code.next_token(last_token)
362
+ else:
363
+ break
364
+ return (first_token, last_token)
365
+
366
+ def visit_bytes(self, node, first_token, last_token):
367
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
368
+ return self.handle_str(first_token, last_token)
369
+
370
+ def handle_str(self, first_token, last_token):
371
+ # type: (util.Token, util.Token) -> Tuple[util.Token, util.Token]
372
+ # Multiple adjacent STRING tokens form a single string.
373
+ last = self._code.next_token(last_token)
374
+ while util.match_token(last, token.STRING):
375
+ last_token = last
376
+ last = self._code.next_token(last_token)
377
+ return (first_token, last_token)
378
+
379
+ def handle_num(self,
380
+ node, # type: AstNode
381
+ value, # type: Union[complex, int, numbers.Number]
382
+ first_token, # type: util.Token
383
+ last_token, # type: util.Token
384
+ ):
385
+ # type: (...) -> Tuple[util.Token, util.Token]
386
+ # A constant like '-1' gets turned into two tokens; this will skip the '-'.
387
+ while util.match_token(last_token, token.OP):
388
+ last_token = self._code.next_token(last_token)
389
+
390
+ if isinstance(value, complex):
391
+ # A complex number like -2j cannot be compared directly to 0
392
+ # A complex number like 1-2j is expressed as a binary operation
393
+ # so we don't need to worry about it
394
+ value = value.imag
395
+
396
+ # This makes sure that the - is included
397
+ if value < 0 and first_token.type == token.NUMBER: # type: ignore[operator]
398
+ first_token = self._code.prev_token(first_token)
399
+ return (first_token, last_token)
400
+
401
+ def visit_num(self, node, first_token, last_token):
402
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
403
+ n = node.n # type: ignore[union-attr] # ast.Num has been removed in python 3.14
404
+ assert isinstance(n, (complex, int, numbers.Number))
405
+ return self.handle_num(node, n, first_token, last_token)
406
+
407
+ def visit_const(self, node, first_token, last_token):
408
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
409
+ assert isinstance(node, ast.Constant) or isinstance(node, nc.Const)
410
+ if isinstance(node.value, numbers.Number):
411
+ return self.handle_num(node, node.value, first_token, last_token)
412
+ elif isinstance(node.value, (str, bytes)):
413
+ return self.visit_str(node, first_token, last_token)
414
+ return (first_token, last_token)
415
+
416
+ visit_constant = visit_const
417
+
418
+ def visit_keyword(self, node, first_token, last_token):
419
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
420
+ # Until python 3.9 (https://bugs.python.org/issue40141),
421
+ # ast.keyword nodes didn't have line info. Astroid has lineno None.
422
+ assert isinstance(node, ast.keyword) or isinstance(node, nc.Keyword)
423
+ if node.arg is not None and getattr(node, 'lineno', None) is None:
424
+ equals = self._code.find_token(first_token, token.OP, '=', reverse=True)
425
+ name = self._code.prev_token(equals)
426
+ util.expect_token(name, token.NAME, node.arg)
427
+ first_token = name
428
+ return (first_token, last_token)
429
+
430
+ def visit_starred(self, node, first_token, last_token):
431
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
432
+ # Astroid has 'Starred' nodes (for "foo(*bar)" type args), but they need to be adjusted.
433
+ if not util.match_token(first_token, token.OP, '*'):
434
+ star = self._code.prev_token(first_token)
435
+ if util.match_token(star, token.OP, '*'):
436
+ first_token = star
437
+ return (first_token, last_token)
438
+
439
+ def visit_assignname(self, node, first_token, last_token):
440
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
441
+ # Astroid may turn 'except' clause into AssignName, but we need to adjust it.
442
+ if util.match_token(first_token, token.NAME, 'except'):
443
+ colon = self._code.find_token(last_token, token.OP, ':')
444
+ first_token = last_token = self._code.prev_token(colon)
445
+ return (first_token, last_token)
446
+
447
+ # Async nodes should typically start with the word 'async'
448
+ # but Python < 3.7 doesn't put the col_offset there
449
+ # AsyncFunctionDef is slightly different because it might have
450
+ # decorators before that, which visit_functiondef handles
451
+ def handle_async(self, node, first_token, last_token):
452
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
453
+ if not first_token.string == 'async':
454
+ first_token = self._code.prev_token(first_token)
455
+ return (first_token, last_token)
456
+
457
+ visit_asyncfor = handle_async
458
+ visit_asyncwith = handle_async
459
+
460
+ def visit_asyncfunctiondef(self,
461
+ node, # type: AstNode
462
+ first_token, # type: util.Token
463
+ last_token, # type: util.Token
464
+ ):
465
+ # type: (...) -> Tuple[util.Token, util.Token]
466
+ if util.match_token(first_token, token.NAME, 'def'):
467
+ # Include the 'async' token
468
+ first_token = self._code.prev_token(first_token)
469
+ return self.visit_functiondef(node, first_token, last_token)
lib/python3.12/site-packages/asttokens/py.typed ADDED
File without changes
lib/python3.12/site-packages/asttokens/version.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = "3.0.1"
lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: h11
3
+ Version: 0.16.0
4
+ Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1
5
+ Home-page: https://github.com/python-hyper/h11
6
+ Author: Nathaniel J. Smith
7
+ Author-email: njs@pobox.com
8
+ License: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: Implementation :: CPython
13
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: System :: Networking
23
+ Requires-Python: >=3.8
24
+ License-File: LICENSE.txt
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: home-page
30
+ Dynamic: license
31
+ Dynamic: license-file
32
+ Dynamic: requires-python
33
+ Dynamic: summary
34
+
35
+ h11
36
+ ===
37
+
38
+ .. image:: https://travis-ci.org/python-hyper/h11.svg?branch=master
39
+ :target: https://travis-ci.org/python-hyper/h11
40
+ :alt: Automated test status
41
+
42
+ .. image:: https://codecov.io/gh/python-hyper/h11/branch/master/graph/badge.svg
43
+ :target: https://codecov.io/gh/python-hyper/h11
44
+ :alt: Test coverage
45
+
46
+ .. image:: https://readthedocs.org/projects/h11/badge/?version=latest
47
+ :target: http://h11.readthedocs.io/en/latest/?badge=latest
48
+ :alt: Documentation Status
49
+
50
+ This is a little HTTP/1.1 library written from scratch in Python,
51
+ heavily inspired by `hyper-h2 <https://hyper-h2.readthedocs.io/>`_.
52
+
53
+ It's a "bring-your-own-I/O" library; h11 contains no IO code
54
+ whatsoever. This means you can hook h11 up to your favorite network
55
+ API, and that could be anything you want: synchronous, threaded,
56
+ asynchronous, or your own implementation of `RFC 6214
57
+ <https://tools.ietf.org/html/rfc6214>`_ -- h11 won't judge you.
58
+ (Compare this to the current state of the art, where every time a `new
59
+ network API <https://trio.readthedocs.io/>`_ comes along then someone
60
+ gets to start over reimplementing the entire HTTP protocol from
61
+ scratch.) Cory Benfield made an `excellent blog post describing the
62
+ benefits of this approach
63
+ <https://lukasa.co.uk/2015/10/The_New_Hyper/>`_, or if you like video
64
+ then here's his `PyCon 2016 talk on the same theme
65
+ <https://www.youtube.com/watch?v=7cC3_jGwl_U>`_.
66
+
67
+ This also means that h11 is not immediately useful out of the box:
68
+ it's a toolkit for building programs that speak HTTP, not something
69
+ that could directly replace ``requests`` or ``twisted.web`` or
70
+ whatever. But h11 makes it much easier to implement something like
71
+ ``requests`` or ``twisted.web``.
72
+
73
+ At a high level, working with h11 goes like this:
74
+
75
+ 1) First, create an ``h11.Connection`` object to track the state of a
76
+ single HTTP/1.1 connection.
77
+
78
+ 2) When you read data off the network, pass it to
79
+ ``conn.receive_data(...)``; you'll get back a list of objects
80
+ representing high-level HTTP "events".
81
+
82
+ 3) When you want to send a high-level HTTP event, create the
83
+ corresponding "event" object and pass it to ``conn.send(...)``;
84
+ this will give you back some bytes that you can then push out
85
+ through the network.
86
+
87
+ For example, a client might instantiate and then send a
88
+ ``h11.Request`` object, then zero or more ``h11.Data`` objects for the
89
+ request body (e.g., if this is a POST), and then a
90
+ ``h11.EndOfMessage`` to indicate the end of the message. Then the
91
+ server would then send back a ``h11.Response``, some ``h11.Data``, and
92
+ its own ``h11.EndOfMessage``. If either side violates the protocol,
93
+ you'll get a ``h11.ProtocolError`` exception.
94
+
95
+ h11 is suitable for implementing both servers and clients, and has a
96
+ pleasantly symmetric API: the events you send as a client are exactly
97
+ the ones that you receive as a server and vice-versa.
98
+
99
+ `Here's an example of a tiny HTTP client
100
+ <https://github.com/python-hyper/h11/blob/master/examples/basic-client.py>`_
101
+
102
+ It also has `a fine manual <https://h11.readthedocs.io/>`_.
103
+
104
+ FAQ
105
+ ---
106
+
107
+ *Whyyyyy?*
108
+
109
+ I wanted to play with HTTP in `Curio
110
+ <https://curio.readthedocs.io/en/latest/tutorial.html>`__ and `Trio
111
+ <https://trio.readthedocs.io>`__, which at the time didn't have any
112
+ HTTP libraries. So I thought, no big deal, Python has, like, a dozen
113
+ different implementations of HTTP, surely I can find one that's
114
+ reusable. I didn't find one, but I did find Cory's call-to-arms
115
+ blog-post. So I figured, well, fine, if I have to implement HTTP from
116
+ scratch, at least I can make sure no-one *else* has to ever again.
117
+
118
+ *Should I use it?*
119
+
120
+ Maybe. You should be aware that it's a very young project. But, it's
121
+ feature complete and has an exhaustive test-suite and complete docs,
122
+ so the next step is for people to try using it and see how it goes
123
+ :-). If you do then please let us know -- if nothing else we'll want
124
+ to talk to you before making any incompatible changes!
125
+
126
+ *What are the features/limitations?*
127
+
128
+ Roughly speaking, it's trying to be a robust, complete, and non-hacky
129
+ implementation of the first "chapter" of the HTTP/1.1 spec: `RFC 7230:
130
+ HTTP/1.1 Message Syntax and Routing
131
+ <https://tools.ietf.org/html/rfc7230>`_. That is, it mostly focuses on
132
+ implementing HTTP at the level of taking bytes on and off the wire,
133
+ and the headers related to that, and tries to be anal about spec
134
+ conformance. It doesn't know about higher-level concerns like URL
135
+ routing, conditional GETs, cross-origin cookie policies, or content
136
+ negotiation. But it does know how to take care of framing,
137
+ cross-version differences in keep-alive handling, and the "obsolete
138
+ line folding" rule, so you can focus your energies on the hard /
139
+ interesting parts for your application, and it tries to support the
140
+ full specification in the sense that any useful HTTP/1.1 conformant
141
+ application should be able to use h11.
142
+
143
+ It's pure Python, and has no dependencies outside of the standard
144
+ library.
145
+
146
+ It has a test suite with 100.0% coverage for both statements and
147
+ branches.
148
+
149
+ Currently it supports Python 3 (testing on 3.8-3.12) and PyPy 3.
150
+ The last Python 2-compatible version was h11 0.11.x.
151
+ (Originally it had a Cython wrapper for `http-parser
152
+ <https://github.com/nodejs/http-parser>`_ and a beautiful nested state
153
+ machine implemented with ``yield from`` to postprocess the output. But
154
+ I had to take these out -- the new *parser* needs fewer lines-of-code
155
+ than the old *parser wrapper*, is written in pure Python, uses no
156
+ exotic language syntax, and has more features. It's sad, really; that
157
+ old state machine was really slick. I just need a few sentences here
158
+ to mourn that.)
159
+
160
+ I don't know how fast it is. I haven't benchmarked or profiled it yet,
161
+ so it's probably got a few pointless hot spots, and I've been trying
162
+ to err on the side of simplicity and robustness instead of
163
+ micro-optimization. But at the architectural level I tried hard to
164
+ avoid fundamentally bad decisions, e.g., I believe that all the
165
+ parsing algorithms remain linear-time even in the face of pathological
166
+ input like slowloris, and there are no byte-by-byte loops. (I also
167
+ believe that it maintains bounded memory usage in the face of
168
+ arbitrary/pathological input.)
169
+
170
+ The whole library is ~800 lines-of-code. You can read and understand
171
+ the whole thing in less than an hour. Most of the energy invested in
172
+ this so far has been spent on trying to keep things simple by
173
+ minimizing special-cases and ad hoc state manipulation; even though it
174
+ is now quite small and simple, I'm still annoyed that I haven't
175
+ figured out how to make it even smaller and simpler. (Unfortunately,
176
+ HTTP does not lend itself to simplicity.)
177
+
178
+ The API is ~feature complete and I don't expect the general outlines
179
+ to change much, but you can't judge an API's ergonomics until you
180
+ actually document and use it, so I'd expect some changes in the
181
+ details.
182
+
183
+ *How do I try it?*
184
+
185
+ .. code-block:: sh
186
+
187
+ $ pip install h11
188
+ $ git clone git@github.com:python-hyper/h11
189
+ $ cd h11/examples
190
+ $ python basic-client.py
191
+
192
+ and go from there.
193
+
194
+ *License?*
195
+
196
+ MIT
197
+
198
+ *Code of conduct?*
199
+
200
+ Contributors are requested to follow our `code of conduct
201
+ <https://github.com/python-hyper/h11/blob/master/CODE_OF_CONDUCT.md>`_ in
202
+ all project spaces.
lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ h11-0.16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ h11-0.16.0.dist-info/METADATA,sha256=KPMmCYrAn8unm48YD5YIfIQf4kViFct7hyqcfVzRnWQ,8348
3
+ h11-0.16.0.dist-info/RECORD,,
4
+ h11-0.16.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
5
+ h11-0.16.0.dist-info/licenses/LICENSE.txt,sha256=N9tbuFkm2yikJ6JYZ_ELEjIAOuob5pzLhRE4rbjm82E,1124
6
+ h11-0.16.0.dist-info/top_level.txt,sha256=F7dC4jl3zeh8TGHEPaWJrMbeuoWbS379Gwdi-Yvdcis,4
7
+ h11/__init__.py,sha256=iO1KzkSO42yZ6ffg-VMgbx_ZVTWGUY00nRYEWn-s3kY,1507
8
+ h11/__pycache__/__init__.cpython-312.pyc,,
9
+ h11/__pycache__/_abnf.cpython-312.pyc,,
10
+ h11/__pycache__/_connection.cpython-312.pyc,,
11
+ h11/__pycache__/_events.cpython-312.pyc,,
12
+ h11/__pycache__/_headers.cpython-312.pyc,,
13
+ h11/__pycache__/_readers.cpython-312.pyc,,
14
+ h11/__pycache__/_receivebuffer.cpython-312.pyc,,
15
+ h11/__pycache__/_state.cpython-312.pyc,,
16
+ h11/__pycache__/_util.cpython-312.pyc,,
17
+ h11/__pycache__/_version.cpython-312.pyc,,
18
+ h11/__pycache__/_writers.cpython-312.pyc,,
19
+ h11/_abnf.py,sha256=ybixr0xsupnkA6GFAyMubuXF6Tc1lb_hF890NgCsfNc,4815
20
+ h11/_connection.py,sha256=k9YRVf6koZqbttBW36xSWaJpWdZwa-xQVU9AHEo9DuI,26863
21
+ h11/_events.py,sha256=I97aXoal1Wu7dkL548BANBUCkOIbe-x5CioYA9IBY14,11792
22
+ h11/_headers.py,sha256=P7D-lBNxHwdLZPLimmYwrPG-9ZkjElvvJZJdZAgSP-4,10412
23
+ h11/_readers.py,sha256=a4RypORUCC3d0q_kxPuBIM7jTD8iLt5X91TH0FsduN4,8590
24
+ h11/_receivebuffer.py,sha256=xrspsdsNgWFxRfQcTXxR8RrdjRXXTK0Io5cQYWpJ1Ws,5252
25
+ h11/_state.py,sha256=_5LG_BGR8FCcFQeBPH-TMHgm_-B-EUcWCnQof_9XjFE,13231
26
+ h11/_util.py,sha256=LWkkjXyJaFlAy6Lt39w73UStklFT5ovcvo0TkY7RYuk,4888
27
+ h11/_version.py,sha256=GVSsbPSPDcOuF6ptfIiXnVJoaEm3ygXbMnqlr_Giahw,686
28
+ h11/_writers.py,sha256=oFKm6PtjeHfbj4RLX7VB7KDc1gIY53gXG3_HR9ltmTA,5081
29
+ h11/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Nathaniel J. Smith <njs@pobox.com> and other contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ h11
lib/python3.12/site-packages/httpcore/__init__.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ._api import request, stream
2
+ from ._async import (
3
+ AsyncConnectionInterface,
4
+ AsyncConnectionPool,
5
+ AsyncHTTP2Connection,
6
+ AsyncHTTP11Connection,
7
+ AsyncHTTPConnection,
8
+ AsyncHTTPProxy,
9
+ AsyncSOCKSProxy,
10
+ )
11
+ from ._backends.base import (
12
+ SOCKET_OPTION,
13
+ AsyncNetworkBackend,
14
+ AsyncNetworkStream,
15
+ NetworkBackend,
16
+ NetworkStream,
17
+ )
18
+ from ._backends.mock import AsyncMockBackend, AsyncMockStream, MockBackend, MockStream
19
+ from ._backends.sync import SyncBackend
20
+ from ._exceptions import (
21
+ ConnectError,
22
+ ConnectionNotAvailable,
23
+ ConnectTimeout,
24
+ LocalProtocolError,
25
+ NetworkError,
26
+ PoolTimeout,
27
+ ProtocolError,
28
+ ProxyError,
29
+ ReadError,
30
+ ReadTimeout,
31
+ RemoteProtocolError,
32
+ TimeoutException,
33
+ UnsupportedProtocol,
34
+ WriteError,
35
+ WriteTimeout,
36
+ )
37
+ from ._models import URL, Origin, Proxy, Request, Response
38
+ from ._ssl import default_ssl_context
39
+ from ._sync import (
40
+ ConnectionInterface,
41
+ ConnectionPool,
42
+ HTTP2Connection,
43
+ HTTP11Connection,
44
+ HTTPConnection,
45
+ HTTPProxy,
46
+ SOCKSProxy,
47
+ )
48
+
49
+ # The 'httpcore.AnyIOBackend' class is conditional on 'anyio' being installed.
50
+ try:
51
+ from ._backends.anyio import AnyIOBackend
52
+ except ImportError: # pragma: nocover
53
+
54
+ class AnyIOBackend: # type: ignore
55
+ def __init__(self, *args, **kwargs): # type: ignore
56
+ msg = (
57
+ "Attempted to use 'httpcore.AnyIOBackend' but 'anyio' is not installed."
58
+ )
59
+ raise RuntimeError(msg)
60
+
61
+
62
+ # The 'httpcore.TrioBackend' class is conditional on 'trio' being installed.
63
+ try:
64
+ from ._backends.trio import TrioBackend
65
+ except ImportError: # pragma: nocover
66
+
67
+ class TrioBackend: # type: ignore
68
+ def __init__(self, *args, **kwargs): # type: ignore
69
+ msg = "Attempted to use 'httpcore.TrioBackend' but 'trio' is not installed."
70
+ raise RuntimeError(msg)
71
+
72
+
73
+ __all__ = [
74
+ # top-level requests
75
+ "request",
76
+ "stream",
77
+ # models
78
+ "Origin",
79
+ "URL",
80
+ "Request",
81
+ "Response",
82
+ "Proxy",
83
+ # async
84
+ "AsyncHTTPConnection",
85
+ "AsyncConnectionPool",
86
+ "AsyncHTTPProxy",
87
+ "AsyncHTTP11Connection",
88
+ "AsyncHTTP2Connection",
89
+ "AsyncConnectionInterface",
90
+ "AsyncSOCKSProxy",
91
+ # sync
92
+ "HTTPConnection",
93
+ "ConnectionPool",
94
+ "HTTPProxy",
95
+ "HTTP11Connection",
96
+ "HTTP2Connection",
97
+ "ConnectionInterface",
98
+ "SOCKSProxy",
99
+ # network backends, implementations
100
+ "SyncBackend",
101
+ "AnyIOBackend",
102
+ "TrioBackend",
103
+ # network backends, mock implementations
104
+ "AsyncMockBackend",
105
+ "AsyncMockStream",
106
+ "MockBackend",
107
+ "MockStream",
108
+ # network backends, interface
109
+ "AsyncNetworkStream",
110
+ "AsyncNetworkBackend",
111
+ "NetworkStream",
112
+ "NetworkBackend",
113
+ # util
114
+ "default_ssl_context",
115
+ "SOCKET_OPTION",
116
+ # exceptions
117
+ "ConnectionNotAvailable",
118
+ "ProxyError",
119
+ "ProtocolError",
120
+ "LocalProtocolError",
121
+ "RemoteProtocolError",
122
+ "UnsupportedProtocol",
123
+ "TimeoutException",
124
+ "PoolTimeout",
125
+ "ConnectTimeout",
126
+ "ReadTimeout",
127
+ "WriteTimeout",
128
+ "NetworkError",
129
+ "ConnectError",
130
+ "ReadError",
131
+ "WriteError",
132
+ ]
133
+
134
+ __version__ = "1.0.9"
135
+
136
+
137
+ __locals = locals()
138
+ for __name in __all__:
139
+ # Exclude SOCKET_OPTION, it causes AttributeError on Python 3.14
140
+ if not __name.startswith(("__", "SOCKET_OPTION")):
141
+ setattr(__locals[__name], "__module__", "httpcore") # noqa
lib/python3.12/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (3.16 kB). View file
 
lib/python3.12/site-packages/httpcore/__pycache__/_api.cpython-312.pyc ADDED
Binary file (3.75 kB). View file
 
lib/python3.12/site-packages/httpcore/__pycache__/_exceptions.cpython-312.pyc ADDED
Binary file (3.18 kB). View file
 
lib/python3.12/site-packages/httpcore/__pycache__/_models.cpython-312.pyc ADDED
Binary file (23.1 kB). View file
 
lib/python3.12/site-packages/httpcore/__pycache__/_ssl.cpython-312.pyc ADDED
Binary file (608 Bytes). View file
 
lib/python3.12/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc ADDED
Binary file (14.2 kB). View file
 
lib/python3.12/site-packages/httpcore/__pycache__/_trace.cpython-312.pyc ADDED
Binary file (5.61 kB). View file
 
lib/python3.12/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc ADDED
Binary file (1.29 kB). View file
 
lib/python3.12/site-packages/httpcore/_api.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import typing
5
+
6
+ from ._models import URL, Extensions, HeaderTypes, Response
7
+ from ._sync.connection_pool import ConnectionPool
8
+
9
+
10
+ def request(
11
+ method: bytes | str,
12
+ url: URL | bytes | str,
13
+ *,
14
+ headers: HeaderTypes = None,
15
+ content: bytes | typing.Iterator[bytes] | None = None,
16
+ extensions: Extensions | None = None,
17
+ ) -> Response:
18
+ """
19
+ Sends an HTTP request, returning the response.
20
+
21
+ ```
22
+ response = httpcore.request("GET", "https://www.example.com/")
23
+ ```
24
+
25
+ Arguments:
26
+ method: The HTTP method for the request. Typically one of `"GET"`,
27
+ `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`.
28
+ url: The URL of the HTTP request. Either as an instance of `httpcore.URL`,
29
+ or as str/bytes.
30
+ headers: The HTTP request headers. Either as a dictionary of str/bytes,
31
+ or as a list of two-tuples of str/bytes.
32
+ content: The content of the request body. Either as bytes,
33
+ or as a bytes iterator.
34
+ extensions: A dictionary of optional extra information included on the request.
35
+ Possible keys include `"timeout"`.
36
+
37
+ Returns:
38
+ An instance of `httpcore.Response`.
39
+ """
40
+ with ConnectionPool() as pool:
41
+ return pool.request(
42
+ method=method,
43
+ url=url,
44
+ headers=headers,
45
+ content=content,
46
+ extensions=extensions,
47
+ )
48
+
49
+
50
+ @contextlib.contextmanager
51
+ def stream(
52
+ method: bytes | str,
53
+ url: URL | bytes | str,
54
+ *,
55
+ headers: HeaderTypes = None,
56
+ content: bytes | typing.Iterator[bytes] | None = None,
57
+ extensions: Extensions | None = None,
58
+ ) -> typing.Iterator[Response]:
59
+ """
60
+ Sends an HTTP request, returning the response within a content manager.
61
+
62
+ ```
63
+ with httpcore.stream("GET", "https://www.example.com/") as response:
64
+ ...
65
+ ```
66
+
67
+ When using the `stream()` function, the body of the response will not be
68
+ automatically read. If you want to access the response body you should
69
+ either use `content = response.read()`, or `for chunk in response.iter_content()`.
70
+
71
+ Arguments:
72
+ method: The HTTP method for the request. Typically one of `"GET"`,
73
+ `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`.
74
+ url: The URL of the HTTP request. Either as an instance of `httpcore.URL`,
75
+ or as str/bytes.
76
+ headers: The HTTP request headers. Either as a dictionary of str/bytes,
77
+ or as a list of two-tuples of str/bytes.
78
+ content: The content of the request body. Either as bytes,
79
+ or as a bytes iterator.
80
+ extensions: A dictionary of optional extra information included on the request.
81
+ Possible keys include `"timeout"`.
82
+
83
+ Returns:
84
+ An instance of `httpcore.Response`.
85
+ """
86
+ with ConnectionPool() as pool:
87
+ with pool.stream(
88
+ method=method,
89
+ url=url,
90
+ headers=headers,
91
+ content=content,
92
+ extensions=extensions,
93
+ ) as response:
94
+ yield response
lib/python3.12/site-packages/httpcore/_async/__init__.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .connection import AsyncHTTPConnection
2
+ from .connection_pool import AsyncConnectionPool
3
+ from .http11 import AsyncHTTP11Connection
4
+ from .http_proxy import AsyncHTTPProxy
5
+ from .interfaces import AsyncConnectionInterface
6
+
7
+ try:
8
+ from .http2 import AsyncHTTP2Connection
9
+ except ImportError: # pragma: nocover
10
+
11
+ class AsyncHTTP2Connection: # type: ignore
12
+ def __init__(self, *args, **kwargs) -> None: # type: ignore
13
+ raise RuntimeError(
14
+ "Attempted to use http2 support, but the `h2` package is not "
15
+ "installed. Use 'pip install httpcore[http2]'."
16
+ )
17
+
18
+
19
+ try:
20
+ from .socks_proxy import AsyncSOCKSProxy
21
+ except ImportError: # pragma: nocover
22
+
23
+ class AsyncSOCKSProxy: # type: ignore
24
+ def __init__(self, *args, **kwargs) -> None: # type: ignore
25
+ raise RuntimeError(
26
+ "Attempted to use SOCKS support, but the `socksio` package is not "
27
+ "installed. Use 'pip install httpcore[socks]'."
28
+ )
29
+
30
+
31
+ __all__ = [
32
+ "AsyncHTTPConnection",
33
+ "AsyncConnectionPool",
34
+ "AsyncHTTPProxy",
35
+ "AsyncHTTP11Connection",
36
+ "AsyncHTTP2Connection",
37
+ "AsyncConnectionInterface",
38
+ "AsyncSOCKSProxy",
39
+ ]
lib/python3.12/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (1.62 kB). View file
 
lib/python3.12/site-packages/httpcore/_async/__pycache__/connection.cpython-312.pyc ADDED
Binary file (11.7 kB). View file
 
lib/python3.12/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc ADDED
Binary file (19.6 kB). View file
 
lib/python3.12/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc ADDED
Binary file (20.1 kB). View file
 
lib/python3.12/site-packages/httpcore/_async/__pycache__/http2.cpython-312.pyc ADDED
Binary file (31.2 kB). View file
 
lib/python3.12/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc ADDED
Binary file (18 kB). View file
 
lib/python3.12/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc ADDED
Binary file (5.75 kB). View file
 
lib/python3.12/site-packages/httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc ADDED
Binary file (16.8 kB). View file
 
lib/python3.12/site-packages/httpcore/_async/connection.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import itertools
4
+ import logging
5
+ import ssl
6
+ import types
7
+ import typing
8
+
9
+ from .._backends.auto import AutoBackend
10
+ from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
11
+ from .._exceptions import ConnectError, ConnectTimeout
12
+ from .._models import Origin, Request, Response
13
+ from .._ssl import default_ssl_context
14
+ from .._synchronization import AsyncLock
15
+ from .._trace import Trace
16
+ from .http11 import AsyncHTTP11Connection
17
+ from .interfaces import AsyncConnectionInterface
18
+
19
+ RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc.
20
+
21
+
22
+ logger = logging.getLogger("httpcore.connection")
23
+
24
+
25
+ def exponential_backoff(factor: float) -> typing.Iterator[float]:
26
+ """
27
+ Generate a geometric sequence that has a ratio of 2 and starts with 0.
28
+
29
+ For example:
30
+ - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...`
31
+ - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...`
32
+ """
33
+ yield 0
34
+ for n in itertools.count():
35
+ yield factor * 2**n
36
+
37
+
38
+ class AsyncHTTPConnection(AsyncConnectionInterface):
39
+ def __init__(
40
+ self,
41
+ origin: Origin,
42
+ ssl_context: ssl.SSLContext | None = None,
43
+ keepalive_expiry: float | None = None,
44
+ http1: bool = True,
45
+ http2: bool = False,
46
+ retries: int = 0,
47
+ local_address: str | None = None,
48
+ uds: str | None = None,
49
+ network_backend: AsyncNetworkBackend | None = None,
50
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
51
+ ) -> None:
52
+ self._origin = origin
53
+ self._ssl_context = ssl_context
54
+ self._keepalive_expiry = keepalive_expiry
55
+ self._http1 = http1
56
+ self._http2 = http2
57
+ self._retries = retries
58
+ self._local_address = local_address
59
+ self._uds = uds
60
+
61
+ self._network_backend: AsyncNetworkBackend = (
62
+ AutoBackend() if network_backend is None else network_backend
63
+ )
64
+ self._connection: AsyncConnectionInterface | None = None
65
+ self._connect_failed: bool = False
66
+ self._request_lock = AsyncLock()
67
+ self._socket_options = socket_options
68
+
69
+ async def handle_async_request(self, request: Request) -> Response:
70
+ if not self.can_handle_request(request.url.origin):
71
+ raise RuntimeError(
72
+ f"Attempted to send request to {request.url.origin} on connection to {self._origin}"
73
+ )
74
+
75
+ try:
76
+ async with self._request_lock:
77
+ if self._connection is None:
78
+ stream = await self._connect(request)
79
+
80
+ ssl_object = stream.get_extra_info("ssl_object")
81
+ http2_negotiated = (
82
+ ssl_object is not None
83
+ and ssl_object.selected_alpn_protocol() == "h2"
84
+ )
85
+ if http2_negotiated or (self._http2 and not self._http1):
86
+ from .http2 import AsyncHTTP2Connection
87
+
88
+ self._connection = AsyncHTTP2Connection(
89
+ origin=self._origin,
90
+ stream=stream,
91
+ keepalive_expiry=self._keepalive_expiry,
92
+ )
93
+ else:
94
+ self._connection = AsyncHTTP11Connection(
95
+ origin=self._origin,
96
+ stream=stream,
97
+ keepalive_expiry=self._keepalive_expiry,
98
+ )
99
+ except BaseException as exc:
100
+ self._connect_failed = True
101
+ raise exc
102
+
103
+ return await self._connection.handle_async_request(request)
104
+
105
+ async def _connect(self, request: Request) -> AsyncNetworkStream:
106
+ timeouts = request.extensions.get("timeout", {})
107
+ sni_hostname = request.extensions.get("sni_hostname", None)
108
+ timeout = timeouts.get("connect", None)
109
+
110
+ retries_left = self._retries
111
+ delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR)
112
+
113
+ while True:
114
+ try:
115
+ if self._uds is None:
116
+ kwargs = {
117
+ "host": self._origin.host.decode("ascii"),
118
+ "port": self._origin.port,
119
+ "local_address": self._local_address,
120
+ "timeout": timeout,
121
+ "socket_options": self._socket_options,
122
+ }
123
+ async with Trace("connect_tcp", logger, request, kwargs) as trace:
124
+ stream = await self._network_backend.connect_tcp(**kwargs)
125
+ trace.return_value = stream
126
+ else:
127
+ kwargs = {
128
+ "path": self._uds,
129
+ "timeout": timeout,
130
+ "socket_options": self._socket_options,
131
+ }
132
+ async with Trace(
133
+ "connect_unix_socket", logger, request, kwargs
134
+ ) as trace:
135
+ stream = await self._network_backend.connect_unix_socket(
136
+ **kwargs
137
+ )
138
+ trace.return_value = stream
139
+
140
+ if self._origin.scheme in (b"https", b"wss"):
141
+ ssl_context = (
142
+ default_ssl_context()
143
+ if self._ssl_context is None
144
+ else self._ssl_context
145
+ )
146
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
147
+ ssl_context.set_alpn_protocols(alpn_protocols)
148
+
149
+ kwargs = {
150
+ "ssl_context": ssl_context,
151
+ "server_hostname": sni_hostname
152
+ or self._origin.host.decode("ascii"),
153
+ "timeout": timeout,
154
+ }
155
+ async with Trace("start_tls", logger, request, kwargs) as trace:
156
+ stream = await stream.start_tls(**kwargs)
157
+ trace.return_value = stream
158
+ return stream
159
+ except (ConnectError, ConnectTimeout):
160
+ if retries_left <= 0:
161
+ raise
162
+ retries_left -= 1
163
+ delay = next(delays)
164
+ async with Trace("retry", logger, request, kwargs) as trace:
165
+ await self._network_backend.sleep(delay)
166
+
167
+ def can_handle_request(self, origin: Origin) -> bool:
168
+ return origin == self._origin
169
+
170
+ async def aclose(self) -> None:
171
+ if self._connection is not None:
172
+ async with Trace("close", logger, None, {}):
173
+ await self._connection.aclose()
174
+
175
+ def is_available(self) -> bool:
176
+ if self._connection is None:
177
+ # If HTTP/2 support is enabled, and the resulting connection could
178
+ # end up as HTTP/2 then we should indicate the connection as being
179
+ # available to service multiple requests.
180
+ return (
181
+ self._http2
182
+ and (self._origin.scheme == b"https" or not self._http1)
183
+ and not self._connect_failed
184
+ )
185
+ return self._connection.is_available()
186
+
187
+ def has_expired(self) -> bool:
188
+ if self._connection is None:
189
+ return self._connect_failed
190
+ return self._connection.has_expired()
191
+
192
+ def is_idle(self) -> bool:
193
+ if self._connection is None:
194
+ return self._connect_failed
195
+ return self._connection.is_idle()
196
+
197
+ def is_closed(self) -> bool:
198
+ if self._connection is None:
199
+ return self._connect_failed
200
+ return self._connection.is_closed()
201
+
202
+ def info(self) -> str:
203
+ if self._connection is None:
204
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
205
+ return self._connection.info()
206
+
207
+ def __repr__(self) -> str:
208
+ return f"<{self.__class__.__name__} [{self.info()}]>"
209
+
210
+ # These context managers are not used in the standard flow, but are
211
+ # useful for testing or working with connection instances directly.
212
+
213
+ async def __aenter__(self) -> AsyncHTTPConnection:
214
+ return self
215
+
216
+ async def __aexit__(
217
+ self,
218
+ exc_type: type[BaseException] | None = None,
219
+ exc_value: BaseException | None = None,
220
+ traceback: types.TracebackType | None = None,
221
+ ) -> None:
222
+ await self.aclose()
lib/python3.12/site-packages/httpcore/_async/connection_pool.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import ssl
4
+ import sys
5
+ import types
6
+ import typing
7
+
8
+ from .._backends.auto import AutoBackend
9
+ from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
10
+ from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
11
+ from .._models import Origin, Proxy, Request, Response
12
+ from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadLock
13
+ from .connection import AsyncHTTPConnection
14
+ from .interfaces import AsyncConnectionInterface, AsyncRequestInterface
15
+
16
+
17
+ class AsyncPoolRequest:
18
+ def __init__(self, request: Request) -> None:
19
+ self.request = request
20
+ self.connection: AsyncConnectionInterface | None = None
21
+ self._connection_acquired = AsyncEvent()
22
+
23
+ def assign_to_connection(self, connection: AsyncConnectionInterface | None) -> None:
24
+ self.connection = connection
25
+ self._connection_acquired.set()
26
+
27
+ def clear_connection(self) -> None:
28
+ self.connection = None
29
+ self._connection_acquired = AsyncEvent()
30
+
31
+ async def wait_for_connection(
32
+ self, timeout: float | None = None
33
+ ) -> AsyncConnectionInterface:
34
+ if self.connection is None:
35
+ await self._connection_acquired.wait(timeout=timeout)
36
+ assert self.connection is not None
37
+ return self.connection
38
+
39
+ def is_queued(self) -> bool:
40
+ return self.connection is None
41
+
42
+
43
+ class AsyncConnectionPool(AsyncRequestInterface):
44
+ """
45
+ A connection pool for making HTTP requests.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ ssl_context: ssl.SSLContext | None = None,
51
+ proxy: Proxy | None = None,
52
+ max_connections: int | None = 10,
53
+ max_keepalive_connections: int | None = None,
54
+ keepalive_expiry: float | None = None,
55
+ http1: bool = True,
56
+ http2: bool = False,
57
+ retries: int = 0,
58
+ local_address: str | None = None,
59
+ uds: str | None = None,
60
+ network_backend: AsyncNetworkBackend | None = None,
61
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
62
+ ) -> None:
63
+ """
64
+ A connection pool for making HTTP requests.
65
+
66
+ Parameters:
67
+ ssl_context: An SSL context to use for verifying connections.
68
+ If not specified, the default `httpcore.default_ssl_context()`
69
+ will be used.
70
+ max_connections: The maximum number of concurrent HTTP connections that
71
+ the pool should allow. Any attempt to send a request on a pool that
72
+ would exceed this amount will block until a connection is available.
73
+ max_keepalive_connections: The maximum number of idle HTTP connections
74
+ that will be maintained in the pool.
75
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
76
+ may be maintained for before being expired from the pool.
77
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
78
+ by the connection pool. Defaults to True.
79
+ http2: A boolean indicating if HTTP/2 requests should be supported by
80
+ the connection pool. Defaults to False.
81
+ retries: The maximum number of retries when trying to establish a
82
+ connection.
83
+ local_address: Local address to connect from. Can also be used to connect
84
+ using a particular address family. Using `local_address="0.0.0.0"`
85
+ will connect using an `AF_INET` address (IPv4), while using
86
+ `local_address="::"` will connect using an `AF_INET6` address (IPv6).
87
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
88
+ network_backend: A backend instance to use for handling network I/O.
89
+ socket_options: Socket options that have to be included
90
+ in the TCP socket when the connection was established.
91
+ """
92
+ self._ssl_context = ssl_context
93
+ self._proxy = proxy
94
+ self._max_connections = (
95
+ sys.maxsize if max_connections is None else max_connections
96
+ )
97
+ self._max_keepalive_connections = (
98
+ sys.maxsize
99
+ if max_keepalive_connections is None
100
+ else max_keepalive_connections
101
+ )
102
+ self._max_keepalive_connections = min(
103
+ self._max_connections, self._max_keepalive_connections
104
+ )
105
+
106
+ self._keepalive_expiry = keepalive_expiry
107
+ self._http1 = http1
108
+ self._http2 = http2
109
+ self._retries = retries
110
+ self._local_address = local_address
111
+ self._uds = uds
112
+
113
+ self._network_backend = (
114
+ AutoBackend() if network_backend is None else network_backend
115
+ )
116
+ self._socket_options = socket_options
117
+
118
+ # The mutable state on a connection pool is the queue of incoming requests,
119
+ # and the set of connections that are servicing those requests.
120
+ self._connections: list[AsyncConnectionInterface] = []
121
+ self._requests: list[AsyncPoolRequest] = []
122
+
123
+ # We only mutate the state of the connection pool within an 'optional_thread_lock'
124
+ # context. This holds a threading lock unless we're running in async mode,
125
+ # in which case it is a no-op.
126
+ self._optional_thread_lock = AsyncThreadLock()
127
+
128
+ def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
129
+ if self._proxy is not None:
130
+ if self._proxy.url.scheme in (b"socks5", b"socks5h"):
131
+ from .socks_proxy import AsyncSocks5Connection
132
+
133
+ return AsyncSocks5Connection(
134
+ proxy_origin=self._proxy.url.origin,
135
+ proxy_auth=self._proxy.auth,
136
+ remote_origin=origin,
137
+ ssl_context=self._ssl_context,
138
+ keepalive_expiry=self._keepalive_expiry,
139
+ http1=self._http1,
140
+ http2=self._http2,
141
+ network_backend=self._network_backend,
142
+ )
143
+ elif origin.scheme == b"http":
144
+ from .http_proxy import AsyncForwardHTTPConnection
145
+
146
+ return AsyncForwardHTTPConnection(
147
+ proxy_origin=self._proxy.url.origin,
148
+ proxy_headers=self._proxy.headers,
149
+ proxy_ssl_context=self._proxy.ssl_context,
150
+ remote_origin=origin,
151
+ keepalive_expiry=self._keepalive_expiry,
152
+ network_backend=self._network_backend,
153
+ )
154
+ from .http_proxy import AsyncTunnelHTTPConnection
155
+
156
+ return AsyncTunnelHTTPConnection(
157
+ proxy_origin=self._proxy.url.origin,
158
+ proxy_headers=self._proxy.headers,
159
+ proxy_ssl_context=self._proxy.ssl_context,
160
+ remote_origin=origin,
161
+ ssl_context=self._ssl_context,
162
+ keepalive_expiry=self._keepalive_expiry,
163
+ http1=self._http1,
164
+ http2=self._http2,
165
+ network_backend=self._network_backend,
166
+ )
167
+
168
+ return AsyncHTTPConnection(
169
+ origin=origin,
170
+ ssl_context=self._ssl_context,
171
+ keepalive_expiry=self._keepalive_expiry,
172
+ http1=self._http1,
173
+ http2=self._http2,
174
+ retries=self._retries,
175
+ local_address=self._local_address,
176
+ uds=self._uds,
177
+ network_backend=self._network_backend,
178
+ socket_options=self._socket_options,
179
+ )
180
+
181
+ @property
182
+ def connections(self) -> list[AsyncConnectionInterface]:
183
+ """
184
+ Return a list of the connections currently in the pool.
185
+
186
+ For example:
187
+
188
+ ```python
189
+ >>> pool.connections
190
+ [
191
+ <AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, ACTIVE, Request Count: 6]>,
192
+ <AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 9]> ,
193
+ <AsyncHTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>,
194
+ ]
195
+ ```
196
+ """
197
+ return list(self._connections)
198
+
199
+ async def handle_async_request(self, request: Request) -> Response:
200
+ """
201
+ Send an HTTP request, and return an HTTP response.
202
+
203
+ This is the core implementation that is called into by `.request()` or `.stream()`.
204
+ """
205
+ scheme = request.url.scheme.decode()
206
+ if scheme == "":
207
+ raise UnsupportedProtocol(
208
+ "Request URL is missing an 'http://' or 'https://' protocol."
209
+ )
210
+ if scheme not in ("http", "https", "ws", "wss"):
211
+ raise UnsupportedProtocol(
212
+ f"Request URL has an unsupported protocol '{scheme}://'."
213
+ )
214
+
215
+ timeouts = request.extensions.get("timeout", {})
216
+ timeout = timeouts.get("pool", None)
217
+
218
+ with self._optional_thread_lock:
219
+ # Add the incoming request to our request queue.
220
+ pool_request = AsyncPoolRequest(request)
221
+ self._requests.append(pool_request)
222
+
223
+ try:
224
+ while True:
225
+ with self._optional_thread_lock:
226
+ # Assign incoming requests to available connections,
227
+ # closing or creating new connections as required.
228
+ closing = self._assign_requests_to_connections()
229
+ await self._close_connections(closing)
230
+
231
+ # Wait until this request has an assigned connection.
232
+ connection = await pool_request.wait_for_connection(timeout=timeout)
233
+
234
+ try:
235
+ # Send the request on the assigned connection.
236
+ response = await connection.handle_async_request(
237
+ pool_request.request
238
+ )
239
+ except ConnectionNotAvailable:
240
+ # In some cases a connection may initially be available to
241
+ # handle a request, but then become unavailable.
242
+ #
243
+ # In this case we clear the connection and try again.
244
+ pool_request.clear_connection()
245
+ else:
246
+ break # pragma: nocover
247
+
248
+ except BaseException as exc:
249
+ with self._optional_thread_lock:
250
+ # For any exception or cancellation we remove the request from
251
+ # the queue, and then re-assign requests to connections.
252
+ self._requests.remove(pool_request)
253
+ closing = self._assign_requests_to_connections()
254
+
255
+ await self._close_connections(closing)
256
+ raise exc from None
257
+
258
+ # Return the response. Note that in this case we still have to manage
259
+ # the point at which the response is closed.
260
+ assert isinstance(response.stream, typing.AsyncIterable)
261
+ return Response(
262
+ status=response.status,
263
+ headers=response.headers,
264
+ content=PoolByteStream(
265
+ stream=response.stream, pool_request=pool_request, pool=self
266
+ ),
267
+ extensions=response.extensions,
268
+ )
269
+
270
+ def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]:
271
+ """
272
+ Manage the state of the connection pool, assigning incoming
273
+ requests to connections as available.
274
+
275
+ Called whenever a new request is added or removed from the pool.
276
+
277
+ Any closing connections are returned, allowing the I/O for closing
278
+ those connections to be handled seperately.
279
+ """
280
+ closing_connections = []
281
+
282
+ # First we handle cleaning up any connections that are closed,
283
+ # have expired their keep-alive, or surplus idle connections.
284
+ for connection in list(self._connections):
285
+ if connection.is_closed():
286
+ # log: "removing closed connection"
287
+ self._connections.remove(connection)
288
+ elif connection.has_expired():
289
+ # log: "closing expired connection"
290
+ self._connections.remove(connection)
291
+ closing_connections.append(connection)
292
+ elif (
293
+ connection.is_idle()
294
+ and len([connection.is_idle() for connection in self._connections])
295
+ > self._max_keepalive_connections
296
+ ):
297
+ # log: "closing idle connection"
298
+ self._connections.remove(connection)
299
+ closing_connections.append(connection)
300
+
301
+ # Assign queued requests to connections.
302
+ queued_requests = [request for request in self._requests if request.is_queued()]
303
+ for pool_request in queued_requests:
304
+ origin = pool_request.request.url.origin
305
+ available_connections = [
306
+ connection
307
+ for connection in self._connections
308
+ if connection.can_handle_request(origin) and connection.is_available()
309
+ ]
310
+ idle_connections = [
311
+ connection for connection in self._connections if connection.is_idle()
312
+ ]
313
+
314
+ # There are three cases for how we may be able to handle the request:
315
+ #
316
+ # 1. There is an existing connection that can handle the request.
317
+ # 2. We can create a new connection to handle the request.
318
+ # 3. We can close an idle connection and then create a new connection
319
+ # to handle the request.
320
+ if available_connections:
321
+ # log: "reusing existing connection"
322
+ connection = available_connections[0]
323
+ pool_request.assign_to_connection(connection)
324
+ elif len(self._connections) < self._max_connections:
325
+ # log: "creating new connection"
326
+ connection = self.create_connection(origin)
327
+ self._connections.append(connection)
328
+ pool_request.assign_to_connection(connection)
329
+ elif idle_connections:
330
+ # log: "closing idle connection"
331
+ connection = idle_connections[0]
332
+ self._connections.remove(connection)
333
+ closing_connections.append(connection)
334
+ # log: "creating new connection"
335
+ connection = self.create_connection(origin)
336
+ self._connections.append(connection)
337
+ pool_request.assign_to_connection(connection)
338
+
339
+ return closing_connections
340
+
341
+ async def _close_connections(self, closing: list[AsyncConnectionInterface]) -> None:
342
+ # Close connections which have been removed from the pool.
343
+ with AsyncShieldCancellation():
344
+ for connection in closing:
345
+ await connection.aclose()
346
+
347
+ async def aclose(self) -> None:
348
+ # Explicitly close the connection pool.
349
+ # Clears all existing requests and connections.
350
+ with self._optional_thread_lock:
351
+ closing_connections = list(self._connections)
352
+ self._connections = []
353
+ await self._close_connections(closing_connections)
354
+
355
+ async def __aenter__(self) -> AsyncConnectionPool:
356
+ return self
357
+
358
+ async def __aexit__(
359
+ self,
360
+ exc_type: type[BaseException] | None = None,
361
+ exc_value: BaseException | None = None,
362
+ traceback: types.TracebackType | None = None,
363
+ ) -> None:
364
+ await self.aclose()
365
+
366
+ def __repr__(self) -> str:
367
+ class_name = self.__class__.__name__
368
+ with self._optional_thread_lock:
369
+ request_is_queued = [request.is_queued() for request in self._requests]
370
+ connection_is_idle = [
371
+ connection.is_idle() for connection in self._connections
372
+ ]
373
+
374
+ num_active_requests = request_is_queued.count(False)
375
+ num_queued_requests = request_is_queued.count(True)
376
+ num_active_connections = connection_is_idle.count(False)
377
+ num_idle_connections = connection_is_idle.count(True)
378
+
379
+ requests_info = (
380
+ f"Requests: {num_active_requests} active, {num_queued_requests} queued"
381
+ )
382
+ connection_info = (
383
+ f"Connections: {num_active_connections} active, {num_idle_connections} idle"
384
+ )
385
+
386
+ return f"<{class_name} [{requests_info} | {connection_info}]>"
387
+
388
+
389
+ class PoolByteStream:
390
+ def __init__(
391
+ self,
392
+ stream: typing.AsyncIterable[bytes],
393
+ pool_request: AsyncPoolRequest,
394
+ pool: AsyncConnectionPool,
395
+ ) -> None:
396
+ self._stream = stream
397
+ self._pool_request = pool_request
398
+ self._pool = pool
399
+ self._closed = False
400
+
401
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
402
+ try:
403
+ async for part in self._stream:
404
+ yield part
405
+ except BaseException as exc:
406
+ await self.aclose()
407
+ raise exc from None
408
+
409
+ async def aclose(self) -> None:
410
+ if not self._closed:
411
+ self._closed = True
412
+ with AsyncShieldCancellation():
413
+ if hasattr(self._stream, "aclose"):
414
+ await self._stream.aclose()
415
+
416
+ with self._pool._optional_thread_lock:
417
+ self._pool._requests.remove(self._pool_request)
418
+ closing = self._pool._assign_requests_to_connections()
419
+
420
+ await self._pool._close_connections(closing)
lib/python3.12/site-packages/httpcore/_async/http11.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import logging
5
+ import ssl
6
+ import time
7
+ import types
8
+ import typing
9
+
10
+ import h11
11
+
12
+ from .._backends.base import AsyncNetworkStream
13
+ from .._exceptions import (
14
+ ConnectionNotAvailable,
15
+ LocalProtocolError,
16
+ RemoteProtocolError,
17
+ WriteError,
18
+ map_exceptions,
19
+ )
20
+ from .._models import Origin, Request, Response
21
+ from .._synchronization import AsyncLock, AsyncShieldCancellation
22
+ from .._trace import Trace
23
+ from .interfaces import AsyncConnectionInterface
24
+
25
+ logger = logging.getLogger("httpcore.http11")
26
+
27
+
28
+ # A subset of `h11.Event` types supported by `_send_event`
29
+ H11SendEvent = typing.Union[
30
+ h11.Request,
31
+ h11.Data,
32
+ h11.EndOfMessage,
33
+ ]
34
+
35
+
36
+ class HTTPConnectionState(enum.IntEnum):
37
+ NEW = 0
38
+ ACTIVE = 1
39
+ IDLE = 2
40
+ CLOSED = 3
41
+
42
+
43
+ class AsyncHTTP11Connection(AsyncConnectionInterface):
44
+ READ_NUM_BYTES = 64 * 1024
45
+ MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024
46
+
47
+ def __init__(
48
+ self,
49
+ origin: Origin,
50
+ stream: AsyncNetworkStream,
51
+ keepalive_expiry: float | None = None,
52
+ ) -> None:
53
+ self._origin = origin
54
+ self._network_stream = stream
55
+ self._keepalive_expiry: float | None = keepalive_expiry
56
+ self._expire_at: float | None = None
57
+ self._state = HTTPConnectionState.NEW
58
+ self._state_lock = AsyncLock()
59
+ self._request_count = 0
60
+ self._h11_state = h11.Connection(
61
+ our_role=h11.CLIENT,
62
+ max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE,
63
+ )
64
+
65
+ async def handle_async_request(self, request: Request) -> Response:
66
+ if not self.can_handle_request(request.url.origin):
67
+ raise RuntimeError(
68
+ f"Attempted to send request to {request.url.origin} on connection "
69
+ f"to {self._origin}"
70
+ )
71
+
72
+ async with self._state_lock:
73
+ if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE):
74
+ self._request_count += 1
75
+ self._state = HTTPConnectionState.ACTIVE
76
+ self._expire_at = None
77
+ else:
78
+ raise ConnectionNotAvailable()
79
+
80
+ try:
81
+ kwargs = {"request": request}
82
+ try:
83
+ async with Trace(
84
+ "send_request_headers", logger, request, kwargs
85
+ ) as trace:
86
+ await self._send_request_headers(**kwargs)
87
+ async with Trace("send_request_body", logger, request, kwargs) as trace:
88
+ await self._send_request_body(**kwargs)
89
+ except WriteError:
90
+ # If we get a write error while we're writing the request,
91
+ # then we supress this error and move on to attempting to
92
+ # read the response. Servers can sometimes close the request
93
+ # pre-emptively and then respond with a well formed HTTP
94
+ # error response.
95
+ pass
96
+
97
+ async with Trace(
98
+ "receive_response_headers", logger, request, kwargs
99
+ ) as trace:
100
+ (
101
+ http_version,
102
+ status,
103
+ reason_phrase,
104
+ headers,
105
+ trailing_data,
106
+ ) = await self._receive_response_headers(**kwargs)
107
+ trace.return_value = (
108
+ http_version,
109
+ status,
110
+ reason_phrase,
111
+ headers,
112
+ )
113
+
114
+ network_stream = self._network_stream
115
+
116
+ # CONNECT or Upgrade request
117
+ if (status == 101) or (
118
+ (request.method == b"CONNECT") and (200 <= status < 300)
119
+ ):
120
+ network_stream = AsyncHTTP11UpgradeStream(network_stream, trailing_data)
121
+
122
+ return Response(
123
+ status=status,
124
+ headers=headers,
125
+ content=HTTP11ConnectionByteStream(self, request),
126
+ extensions={
127
+ "http_version": http_version,
128
+ "reason_phrase": reason_phrase,
129
+ "network_stream": network_stream,
130
+ },
131
+ )
132
+ except BaseException as exc:
133
+ with AsyncShieldCancellation():
134
+ async with Trace("response_closed", logger, request) as trace:
135
+ await self._response_closed()
136
+ raise exc
137
+
138
+ # Sending the request...
139
+
140
+ async def _send_request_headers(self, request: Request) -> None:
141
+ timeouts = request.extensions.get("timeout", {})
142
+ timeout = timeouts.get("write", None)
143
+
144
+ with map_exceptions({h11.LocalProtocolError: LocalProtocolError}):
145
+ event = h11.Request(
146
+ method=request.method,
147
+ target=request.url.target,
148
+ headers=request.headers,
149
+ )
150
+ await self._send_event(event, timeout=timeout)
151
+
152
+ async def _send_request_body(self, request: Request) -> None:
153
+ timeouts = request.extensions.get("timeout", {})
154
+ timeout = timeouts.get("write", None)
155
+
156
+ assert isinstance(request.stream, typing.AsyncIterable)
157
+ async for chunk in request.stream:
158
+ event = h11.Data(data=chunk)
159
+ await self._send_event(event, timeout=timeout)
160
+
161
+ await self._send_event(h11.EndOfMessage(), timeout=timeout)
162
+
163
+ async def _send_event(self, event: h11.Event, timeout: float | None = None) -> None:
164
+ bytes_to_send = self._h11_state.send(event)
165
+ if bytes_to_send is not None:
166
+ await self._network_stream.write(bytes_to_send, timeout=timeout)
167
+
168
+ # Receiving the response...
169
+
170
+ async def _receive_response_headers(
171
+ self, request: Request
172
+ ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]:
173
+ timeouts = request.extensions.get("timeout", {})
174
+ timeout = timeouts.get("read", None)
175
+
176
+ while True:
177
+ event = await self._receive_event(timeout=timeout)
178
+ if isinstance(event, h11.Response):
179
+ break
180
+ if (
181
+ isinstance(event, h11.InformationalResponse)
182
+ and event.status_code == 101
183
+ ):
184
+ break
185
+
186
+ http_version = b"HTTP/" + event.http_version
187
+
188
+ # h11 version 0.11+ supports a `raw_items` interface to get the
189
+ # raw header casing, rather than the enforced lowercase headers.
190
+ headers = event.headers.raw_items()
191
+
192
+ trailing_data, _ = self._h11_state.trailing_data
193
+
194
+ return http_version, event.status_code, event.reason, headers, trailing_data
195
+
196
+ async def _receive_response_body(
197
+ self, request: Request
198
+ ) -> typing.AsyncIterator[bytes]:
199
+ timeouts = request.extensions.get("timeout", {})
200
+ timeout = timeouts.get("read", None)
201
+
202
+ while True:
203
+ event = await self._receive_event(timeout=timeout)
204
+ if isinstance(event, h11.Data):
205
+ yield bytes(event.data)
206
+ elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)):
207
+ break
208
+
209
+ async def _receive_event(
210
+ self, timeout: float | None = None
211
+ ) -> h11.Event | type[h11.PAUSED]:
212
+ while True:
213
+ with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}):
214
+ event = self._h11_state.next_event()
215
+
216
+ if event is h11.NEED_DATA:
217
+ data = await self._network_stream.read(
218
+ self.READ_NUM_BYTES, timeout=timeout
219
+ )
220
+
221
+ # If we feed this case through h11 we'll raise an exception like:
222
+ #
223
+ # httpcore.RemoteProtocolError: can't handle event type
224
+ # ConnectionClosed when role=SERVER and state=SEND_RESPONSE
225
+ #
226
+ # Which is accurate, but not very informative from an end-user
227
+ # perspective. Instead we handle this case distinctly and treat
228
+ # it as a ConnectError.
229
+ if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE:
230
+ msg = "Server disconnected without sending a response."
231
+ raise RemoteProtocolError(msg)
232
+
233
+ self._h11_state.receive_data(data)
234
+ else:
235
+ # mypy fails to narrow the type in the above if statement above
236
+ return event # type: ignore[return-value]
237
+
238
+ async def _response_closed(self) -> None:
239
+ async with self._state_lock:
240
+ if (
241
+ self._h11_state.our_state is h11.DONE
242
+ and self._h11_state.their_state is h11.DONE
243
+ ):
244
+ self._state = HTTPConnectionState.IDLE
245
+ self._h11_state.start_next_cycle()
246
+ if self._keepalive_expiry is not None:
247
+ now = time.monotonic()
248
+ self._expire_at = now + self._keepalive_expiry
249
+ else:
250
+ await self.aclose()
251
+
252
+ # Once the connection is no longer required...
253
+
254
+ async def aclose(self) -> None:
255
+ # Note that this method unilaterally closes the connection, and does
256
+ # not have any kind of locking in place around it.
257
+ self._state = HTTPConnectionState.CLOSED
258
+ await self._network_stream.aclose()
259
+
260
+ # The AsyncConnectionInterface methods provide information about the state of
261
+ # the connection, allowing for a connection pooling implementation to
262
+ # determine when to reuse and when to close the connection...
263
+
264
+ def can_handle_request(self, origin: Origin) -> bool:
265
+ return origin == self._origin
266
+
267
+ def is_available(self) -> bool:
268
+ # Note that HTTP/1.1 connections in the "NEW" state are not treated as
269
+ # being "available". The control flow which created the connection will
270
+ # be able to send an outgoing request, but the connection will not be
271
+ # acquired from the connection pool for any other request.
272
+ return self._state == HTTPConnectionState.IDLE
273
+
274
+ def has_expired(self) -> bool:
275
+ now = time.monotonic()
276
+ keepalive_expired = self._expire_at is not None and now > self._expire_at
277
+
278
+ # If the HTTP connection is idle but the socket is readable, then the
279
+ # only valid state is that the socket is about to return b"", indicating
280
+ # a server-initiated disconnect.
281
+ server_disconnected = (
282
+ self._state == HTTPConnectionState.IDLE
283
+ and self._network_stream.get_extra_info("is_readable")
284
+ )
285
+
286
+ return keepalive_expired or server_disconnected
287
+
288
+ def is_idle(self) -> bool:
289
+ return self._state == HTTPConnectionState.IDLE
290
+
291
+ def is_closed(self) -> bool:
292
+ return self._state == HTTPConnectionState.CLOSED
293
+
294
+ def info(self) -> str:
295
+ origin = str(self._origin)
296
+ return (
297
+ f"{origin!r}, HTTP/1.1, {self._state.name}, "
298
+ f"Request Count: {self._request_count}"
299
+ )
300
+
301
+ def __repr__(self) -> str:
302
+ class_name = self.__class__.__name__
303
+ origin = str(self._origin)
304
+ return (
305
+ f"<{class_name} [{origin!r}, {self._state.name}, "
306
+ f"Request Count: {self._request_count}]>"
307
+ )
308
+
309
+ # These context managers are not used in the standard flow, but are
310
+ # useful for testing or working with connection instances directly.
311
+
312
+ async def __aenter__(self) -> AsyncHTTP11Connection:
313
+ return self
314
+
315
+ async def __aexit__(
316
+ self,
317
+ exc_type: type[BaseException] | None = None,
318
+ exc_value: BaseException | None = None,
319
+ traceback: types.TracebackType | None = None,
320
+ ) -> None:
321
+ await self.aclose()
322
+
323
+
324
+ class HTTP11ConnectionByteStream:
325
+ def __init__(self, connection: AsyncHTTP11Connection, request: Request) -> None:
326
+ self._connection = connection
327
+ self._request = request
328
+ self._closed = False
329
+
330
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
331
+ kwargs = {"request": self._request}
332
+ try:
333
+ async with Trace("receive_response_body", logger, self._request, kwargs):
334
+ async for chunk in self._connection._receive_response_body(**kwargs):
335
+ yield chunk
336
+ except BaseException as exc:
337
+ # If we get an exception while streaming the response,
338
+ # we want to close the response (and possibly the connection)
339
+ # before raising that exception.
340
+ with AsyncShieldCancellation():
341
+ await self.aclose()
342
+ raise exc
343
+
344
+ async def aclose(self) -> None:
345
+ if not self._closed:
346
+ self._closed = True
347
+ async with Trace("response_closed", logger, self._request):
348
+ await self._connection._response_closed()
349
+
350
+
351
+ class AsyncHTTP11UpgradeStream(AsyncNetworkStream):
352
+ def __init__(self, stream: AsyncNetworkStream, leading_data: bytes) -> None:
353
+ self._stream = stream
354
+ self._leading_data = leading_data
355
+
356
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
357
+ if self._leading_data:
358
+ buffer = self._leading_data[:max_bytes]
359
+ self._leading_data = self._leading_data[max_bytes:]
360
+ return buffer
361
+ else:
362
+ return await self._stream.read(max_bytes, timeout)
363
+
364
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
365
+ await self._stream.write(buffer, timeout)
366
+
367
+ async def aclose(self) -> None:
368
+ await self._stream.aclose()
369
+
370
+ async def start_tls(
371
+ self,
372
+ ssl_context: ssl.SSLContext,
373
+ server_hostname: str | None = None,
374
+ timeout: float | None = None,
375
+ ) -> AsyncNetworkStream:
376
+ return await self._stream.start_tls(ssl_context, server_hostname, timeout)
377
+
378
+ def get_extra_info(self, info: str) -> typing.Any:
379
+ return self._stream.get_extra_info(info)
lib/python3.12/site-packages/httpcore/_async/http2.py ADDED
@@ -0,0 +1,592 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import logging
5
+ import time
6
+ import types
7
+ import typing
8
+
9
+ import h2.config
10
+ import h2.connection
11
+ import h2.events
12
+ import h2.exceptions
13
+ import h2.settings
14
+
15
+ from .._backends.base import AsyncNetworkStream
16
+ from .._exceptions import (
17
+ ConnectionNotAvailable,
18
+ LocalProtocolError,
19
+ RemoteProtocolError,
20
+ )
21
+ from .._models import Origin, Request, Response
22
+ from .._synchronization import AsyncLock, AsyncSemaphore, AsyncShieldCancellation
23
+ from .._trace import Trace
24
+ from .interfaces import AsyncConnectionInterface
25
+
26
+ logger = logging.getLogger("httpcore.http2")
27
+
28
+
29
+ def has_body_headers(request: Request) -> bool:
30
+ return any(
31
+ k.lower() == b"content-length" or k.lower() == b"transfer-encoding"
32
+ for k, v in request.headers
33
+ )
34
+
35
+
36
+ class HTTPConnectionState(enum.IntEnum):
37
+ ACTIVE = 1
38
+ IDLE = 2
39
+ CLOSED = 3
40
+
41
+
42
+ class AsyncHTTP2Connection(AsyncConnectionInterface):
43
+ READ_NUM_BYTES = 64 * 1024
44
+ CONFIG = h2.config.H2Configuration(validate_inbound_headers=False)
45
+
46
+ def __init__(
47
+ self,
48
+ origin: Origin,
49
+ stream: AsyncNetworkStream,
50
+ keepalive_expiry: float | None = None,
51
+ ):
52
+ self._origin = origin
53
+ self._network_stream = stream
54
+ self._keepalive_expiry: float | None = keepalive_expiry
55
+ self._h2_state = h2.connection.H2Connection(config=self.CONFIG)
56
+ self._state = HTTPConnectionState.IDLE
57
+ self._expire_at: float | None = None
58
+ self._request_count = 0
59
+ self._init_lock = AsyncLock()
60
+ self._state_lock = AsyncLock()
61
+ self._read_lock = AsyncLock()
62
+ self._write_lock = AsyncLock()
63
+ self._sent_connection_init = False
64
+ self._used_all_stream_ids = False
65
+ self._connection_error = False
66
+
67
+ # Mapping from stream ID to response stream events.
68
+ self._events: dict[
69
+ int,
70
+ list[
71
+ h2.events.ResponseReceived
72
+ | h2.events.DataReceived
73
+ | h2.events.StreamEnded
74
+ | h2.events.StreamReset,
75
+ ],
76
+ ] = {}
77
+
78
+ # Connection terminated events are stored as state since
79
+ # we need to handle them for all streams.
80
+ self._connection_terminated: h2.events.ConnectionTerminated | None = None
81
+
82
+ self._read_exception: Exception | None = None
83
+ self._write_exception: Exception | None = None
84
+
85
+ async def handle_async_request(self, request: Request) -> Response:
86
+ if not self.can_handle_request(request.url.origin):
87
+ # This cannot occur in normal operation, since the connection pool
88
+ # will only send requests on connections that handle them.
89
+ # It's in place simply for resilience as a guard against incorrect
90
+ # usage, for anyone working directly with httpcore connections.
91
+ raise RuntimeError(
92
+ f"Attempted to send request to {request.url.origin} on connection "
93
+ f"to {self._origin}"
94
+ )
95
+
96
+ async with self._state_lock:
97
+ if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE):
98
+ self._request_count += 1
99
+ self._expire_at = None
100
+ self._state = HTTPConnectionState.ACTIVE
101
+ else:
102
+ raise ConnectionNotAvailable()
103
+
104
+ async with self._init_lock:
105
+ if not self._sent_connection_init:
106
+ try:
107
+ sci_kwargs = {"request": request}
108
+ async with Trace(
109
+ "send_connection_init", logger, request, sci_kwargs
110
+ ):
111
+ await self._send_connection_init(**sci_kwargs)
112
+ except BaseException as exc:
113
+ with AsyncShieldCancellation():
114
+ await self.aclose()
115
+ raise exc
116
+
117
+ self._sent_connection_init = True
118
+
119
+ # Initially start with just 1 until the remote server provides
120
+ # its max_concurrent_streams value
121
+ self._max_streams = 1
122
+
123
+ local_settings_max_streams = (
124
+ self._h2_state.local_settings.max_concurrent_streams
125
+ )
126
+ self._max_streams_semaphore = AsyncSemaphore(local_settings_max_streams)
127
+
128
+ for _ in range(local_settings_max_streams - self._max_streams):
129
+ await self._max_streams_semaphore.acquire()
130
+
131
+ await self._max_streams_semaphore.acquire()
132
+
133
+ try:
134
+ stream_id = self._h2_state.get_next_available_stream_id()
135
+ self._events[stream_id] = []
136
+ except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover
137
+ self._used_all_stream_ids = True
138
+ self._request_count -= 1
139
+ raise ConnectionNotAvailable()
140
+
141
+ try:
142
+ kwargs = {"request": request, "stream_id": stream_id}
143
+ async with Trace("send_request_headers", logger, request, kwargs):
144
+ await self._send_request_headers(request=request, stream_id=stream_id)
145
+ async with Trace("send_request_body", logger, request, kwargs):
146
+ await self._send_request_body(request=request, stream_id=stream_id)
147
+ async with Trace(
148
+ "receive_response_headers", logger, request, kwargs
149
+ ) as trace:
150
+ status, headers = await self._receive_response(
151
+ request=request, stream_id=stream_id
152
+ )
153
+ trace.return_value = (status, headers)
154
+
155
+ return Response(
156
+ status=status,
157
+ headers=headers,
158
+ content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id),
159
+ extensions={
160
+ "http_version": b"HTTP/2",
161
+ "network_stream": self._network_stream,
162
+ "stream_id": stream_id,
163
+ },
164
+ )
165
+ except BaseException as exc: # noqa: PIE786
166
+ with AsyncShieldCancellation():
167
+ kwargs = {"stream_id": stream_id}
168
+ async with Trace("response_closed", logger, request, kwargs):
169
+ await self._response_closed(stream_id=stream_id)
170
+
171
+ if isinstance(exc, h2.exceptions.ProtocolError):
172
+ # One case where h2 can raise a protocol error is when a
173
+ # closed frame has been seen by the state machine.
174
+ #
175
+ # This happens when one stream is reading, and encounters
176
+ # a GOAWAY event. Other flows of control may then raise
177
+ # a protocol error at any point they interact with the 'h2_state'.
178
+ #
179
+ # In this case we'll have stored the event, and should raise
180
+ # it as a RemoteProtocolError.
181
+ if self._connection_terminated: # pragma: nocover
182
+ raise RemoteProtocolError(self._connection_terminated)
183
+ # If h2 raises a protocol error in some other state then we
184
+ # must somehow have made a protocol violation.
185
+ raise LocalProtocolError(exc) # pragma: nocover
186
+
187
+ raise exc
188
+
189
+ async def _send_connection_init(self, request: Request) -> None:
190
+ """
191
+ The HTTP/2 connection requires some initial setup before we can start
192
+ using individual request/response streams on it.
193
+ """
194
+ # Need to set these manually here instead of manipulating via
195
+ # __setitem__() otherwise the H2Connection will emit SettingsUpdate
196
+ # frames in addition to sending the undesired defaults.
197
+ self._h2_state.local_settings = h2.settings.Settings(
198
+ client=True,
199
+ initial_values={
200
+ # Disable PUSH_PROMISE frames from the server since we don't do anything
201
+ # with them for now. Maybe when we support caching?
202
+ h2.settings.SettingCodes.ENABLE_PUSH: 0,
203
+ # These two are taken from h2 for safe defaults
204
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
205
+ h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536,
206
+ },
207
+ )
208
+
209
+ # Some websites (*cough* Yahoo *cough*) balk at this setting being
210
+ # present in the initial handshake since it's not defined in the original
211
+ # RFC despite the RFC mandating ignoring settings you don't know about.
212
+ del self._h2_state.local_settings[
213
+ h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL
214
+ ]
215
+
216
+ self._h2_state.initiate_connection()
217
+ self._h2_state.increment_flow_control_window(2**24)
218
+ await self._write_outgoing_data(request)
219
+
220
+ # Sending the request...
221
+
222
+ async def _send_request_headers(self, request: Request, stream_id: int) -> None:
223
+ """
224
+ Send the request headers to a given stream ID.
225
+ """
226
+ end_stream = not has_body_headers(request)
227
+
228
+ # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'.
229
+ # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require
230
+ # HTTP/1.1 style headers, and map them appropriately if we end up on
231
+ # an HTTP/2 connection.
232
+ authority = [v for k, v in request.headers if k.lower() == b"host"][0]
233
+
234
+ headers = [
235
+ (b":method", request.method),
236
+ (b":authority", authority),
237
+ (b":scheme", request.url.scheme),
238
+ (b":path", request.url.target),
239
+ ] + [
240
+ (k.lower(), v)
241
+ for k, v in request.headers
242
+ if k.lower()
243
+ not in (
244
+ b"host",
245
+ b"transfer-encoding",
246
+ )
247
+ ]
248
+
249
+ self._h2_state.send_headers(stream_id, headers, end_stream=end_stream)
250
+ self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id)
251
+ await self._write_outgoing_data(request)
252
+
253
+ async def _send_request_body(self, request: Request, stream_id: int) -> None:
254
+ """
255
+ Iterate over the request body sending it to a given stream ID.
256
+ """
257
+ if not has_body_headers(request):
258
+ return
259
+
260
+ assert isinstance(request.stream, typing.AsyncIterable)
261
+ async for data in request.stream:
262
+ await self._send_stream_data(request, stream_id, data)
263
+ await self._send_end_stream(request, stream_id)
264
+
265
+ async def _send_stream_data(
266
+ self, request: Request, stream_id: int, data: bytes
267
+ ) -> None:
268
+ """
269
+ Send a single chunk of data in one or more data frames.
270
+ """
271
+ while data:
272
+ max_flow = await self._wait_for_outgoing_flow(request, stream_id)
273
+ chunk_size = min(len(data), max_flow)
274
+ chunk, data = data[:chunk_size], data[chunk_size:]
275
+ self._h2_state.send_data(stream_id, chunk)
276
+ await self._write_outgoing_data(request)
277
+
278
+ async def _send_end_stream(self, request: Request, stream_id: int) -> None:
279
+ """
280
+ Send an empty data frame on on a given stream ID with the END_STREAM flag set.
281
+ """
282
+ self._h2_state.end_stream(stream_id)
283
+ await self._write_outgoing_data(request)
284
+
285
+ # Receiving the response...
286
+
287
+ async def _receive_response(
288
+ self, request: Request, stream_id: int
289
+ ) -> tuple[int, list[tuple[bytes, bytes]]]:
290
+ """
291
+ Return the response status code and headers for a given stream ID.
292
+ """
293
+ while True:
294
+ event = await self._receive_stream_event(request, stream_id)
295
+ if isinstance(event, h2.events.ResponseReceived):
296
+ break
297
+
298
+ status_code = 200
299
+ headers = []
300
+ assert event.headers is not None
301
+ for k, v in event.headers:
302
+ if k == b":status":
303
+ status_code = int(v.decode("ascii", errors="ignore"))
304
+ elif not k.startswith(b":"):
305
+ headers.append((k, v))
306
+
307
+ return (status_code, headers)
308
+
309
+ async def _receive_response_body(
310
+ self, request: Request, stream_id: int
311
+ ) -> typing.AsyncIterator[bytes]:
312
+ """
313
+ Iterator that returns the bytes of the response body for a given stream ID.
314
+ """
315
+ while True:
316
+ event = await self._receive_stream_event(request, stream_id)
317
+ if isinstance(event, h2.events.DataReceived):
318
+ assert event.flow_controlled_length is not None
319
+ assert event.data is not None
320
+ amount = event.flow_controlled_length
321
+ self._h2_state.acknowledge_received_data(amount, stream_id)
322
+ await self._write_outgoing_data(request)
323
+ yield event.data
324
+ elif isinstance(event, h2.events.StreamEnded):
325
+ break
326
+
327
+ async def _receive_stream_event(
328
+ self, request: Request, stream_id: int
329
+ ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded:
330
+ """
331
+ Return the next available event for a given stream ID.
332
+
333
+ Will read more data from the network if required.
334
+ """
335
+ while not self._events.get(stream_id):
336
+ await self._receive_events(request, stream_id)
337
+ event = self._events[stream_id].pop(0)
338
+ if isinstance(event, h2.events.StreamReset):
339
+ raise RemoteProtocolError(event)
340
+ return event
341
+
342
+ async def _receive_events(
343
+ self, request: Request, stream_id: int | None = None
344
+ ) -> None:
345
+ """
346
+ Read some data from the network until we see one or more events
347
+ for a given stream ID.
348
+ """
349
+ async with self._read_lock:
350
+ if self._connection_terminated is not None:
351
+ last_stream_id = self._connection_terminated.last_stream_id
352
+ if stream_id and last_stream_id and stream_id > last_stream_id:
353
+ self._request_count -= 1
354
+ raise ConnectionNotAvailable()
355
+ raise RemoteProtocolError(self._connection_terminated)
356
+
357
+ # This conditional is a bit icky. We don't want to block reading if we've
358
+ # actually got an event to return for a given stream. We need to do that
359
+ # check *within* the atomic read lock. Though it also need to be optional,
360
+ # because when we call it from `_wait_for_outgoing_flow` we *do* want to
361
+ # block until we've available flow control, event when we have events
362
+ # pending for the stream ID we're attempting to send on.
363
+ if stream_id is None or not self._events.get(stream_id):
364
+ events = await self._read_incoming_data(request)
365
+ for event in events:
366
+ if isinstance(event, h2.events.RemoteSettingsChanged):
367
+ async with Trace(
368
+ "receive_remote_settings", logger, request
369
+ ) as trace:
370
+ await self._receive_remote_settings_change(event)
371
+ trace.return_value = event
372
+
373
+ elif isinstance(
374
+ event,
375
+ (
376
+ h2.events.ResponseReceived,
377
+ h2.events.DataReceived,
378
+ h2.events.StreamEnded,
379
+ h2.events.StreamReset,
380
+ ),
381
+ ):
382
+ if event.stream_id in self._events:
383
+ self._events[event.stream_id].append(event)
384
+
385
+ elif isinstance(event, h2.events.ConnectionTerminated):
386
+ self._connection_terminated = event
387
+
388
+ await self._write_outgoing_data(request)
389
+
390
+ async def _receive_remote_settings_change(
391
+ self, event: h2.events.RemoteSettingsChanged
392
+ ) -> None:
393
+ max_concurrent_streams = event.changed_settings.get(
394
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS
395
+ )
396
+ if max_concurrent_streams:
397
+ new_max_streams = min(
398
+ max_concurrent_streams.new_value,
399
+ self._h2_state.local_settings.max_concurrent_streams,
400
+ )
401
+ if new_max_streams and new_max_streams != self._max_streams:
402
+ while new_max_streams > self._max_streams:
403
+ await self._max_streams_semaphore.release()
404
+ self._max_streams += 1
405
+ while new_max_streams < self._max_streams:
406
+ await self._max_streams_semaphore.acquire()
407
+ self._max_streams -= 1
408
+
409
+ async def _response_closed(self, stream_id: int) -> None:
410
+ await self._max_streams_semaphore.release()
411
+ del self._events[stream_id]
412
+ async with self._state_lock:
413
+ if self._connection_terminated and not self._events:
414
+ await self.aclose()
415
+
416
+ elif self._state == HTTPConnectionState.ACTIVE and not self._events:
417
+ self._state = HTTPConnectionState.IDLE
418
+ if self._keepalive_expiry is not None:
419
+ now = time.monotonic()
420
+ self._expire_at = now + self._keepalive_expiry
421
+ if self._used_all_stream_ids: # pragma: nocover
422
+ await self.aclose()
423
+
424
+ async def aclose(self) -> None:
425
+ # Note that this method unilaterally closes the connection, and does
426
+ # not have any kind of locking in place around it.
427
+ self._h2_state.close_connection()
428
+ self._state = HTTPConnectionState.CLOSED
429
+ await self._network_stream.aclose()
430
+
431
+ # Wrappers around network read/write operations...
432
+
433
+ async def _read_incoming_data(self, request: Request) -> list[h2.events.Event]:
434
+ timeouts = request.extensions.get("timeout", {})
435
+ timeout = timeouts.get("read", None)
436
+
437
+ if self._read_exception is not None:
438
+ raise self._read_exception # pragma: nocover
439
+
440
+ try:
441
+ data = await self._network_stream.read(self.READ_NUM_BYTES, timeout)
442
+ if data == b"":
443
+ raise RemoteProtocolError("Server disconnected")
444
+ except Exception as exc:
445
+ # If we get a network error we should:
446
+ #
447
+ # 1. Save the exception and just raise it immediately on any future reads.
448
+ # (For example, this means that a single read timeout or disconnect will
449
+ # immediately close all pending streams. Without requiring multiple
450
+ # sequential timeouts.)
451
+ # 2. Mark the connection as errored, so that we don't accept any other
452
+ # incoming requests.
453
+ self._read_exception = exc
454
+ self._connection_error = True
455
+ raise exc
456
+
457
+ events: list[h2.events.Event] = self._h2_state.receive_data(data)
458
+
459
+ return events
460
+
461
+ async def _write_outgoing_data(self, request: Request) -> None:
462
+ timeouts = request.extensions.get("timeout", {})
463
+ timeout = timeouts.get("write", None)
464
+
465
+ async with self._write_lock:
466
+ data_to_send = self._h2_state.data_to_send()
467
+
468
+ if self._write_exception is not None:
469
+ raise self._write_exception # pragma: nocover
470
+
471
+ try:
472
+ await self._network_stream.write(data_to_send, timeout)
473
+ except Exception as exc: # pragma: nocover
474
+ # If we get a network error we should:
475
+ #
476
+ # 1. Save the exception and just raise it immediately on any future write.
477
+ # (For example, this means that a single write timeout or disconnect will
478
+ # immediately close all pending streams. Without requiring multiple
479
+ # sequential timeouts.)
480
+ # 2. Mark the connection as errored, so that we don't accept any other
481
+ # incoming requests.
482
+ self._write_exception = exc
483
+ self._connection_error = True
484
+ raise exc
485
+
486
+ # Flow control...
487
+
488
+ async def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int:
489
+ """
490
+ Returns the maximum allowable outgoing flow for a given stream.
491
+
492
+ If the allowable flow is zero, then waits on the network until
493
+ WindowUpdated frames have increased the flow rate.
494
+ https://tools.ietf.org/html/rfc7540#section-6.9
495
+ """
496
+ local_flow: int = self._h2_state.local_flow_control_window(stream_id)
497
+ max_frame_size: int = self._h2_state.max_outbound_frame_size
498
+ flow = min(local_flow, max_frame_size)
499
+ while flow == 0:
500
+ await self._receive_events(request)
501
+ local_flow = self._h2_state.local_flow_control_window(stream_id)
502
+ max_frame_size = self._h2_state.max_outbound_frame_size
503
+ flow = min(local_flow, max_frame_size)
504
+ return flow
505
+
506
+ # Interface for connection pooling...
507
+
508
+ def can_handle_request(self, origin: Origin) -> bool:
509
+ return origin == self._origin
510
+
511
+ def is_available(self) -> bool:
512
+ return (
513
+ self._state != HTTPConnectionState.CLOSED
514
+ and not self._connection_error
515
+ and not self._used_all_stream_ids
516
+ and not (
517
+ self._h2_state.state_machine.state
518
+ == h2.connection.ConnectionState.CLOSED
519
+ )
520
+ )
521
+
522
+ def has_expired(self) -> bool:
523
+ now = time.monotonic()
524
+ return self._expire_at is not None and now > self._expire_at
525
+
526
+ def is_idle(self) -> bool:
527
+ return self._state == HTTPConnectionState.IDLE
528
+
529
+ def is_closed(self) -> bool:
530
+ return self._state == HTTPConnectionState.CLOSED
531
+
532
+ def info(self) -> str:
533
+ origin = str(self._origin)
534
+ return (
535
+ f"{origin!r}, HTTP/2, {self._state.name}, "
536
+ f"Request Count: {self._request_count}"
537
+ )
538
+
539
+ def __repr__(self) -> str:
540
+ class_name = self.__class__.__name__
541
+ origin = str(self._origin)
542
+ return (
543
+ f"<{class_name} [{origin!r}, {self._state.name}, "
544
+ f"Request Count: {self._request_count}]>"
545
+ )
546
+
547
+ # These context managers are not used in the standard flow, but are
548
+ # useful for testing or working with connection instances directly.
549
+
550
+ async def __aenter__(self) -> AsyncHTTP2Connection:
551
+ return self
552
+
553
+ async def __aexit__(
554
+ self,
555
+ exc_type: type[BaseException] | None = None,
556
+ exc_value: BaseException | None = None,
557
+ traceback: types.TracebackType | None = None,
558
+ ) -> None:
559
+ await self.aclose()
560
+
561
+
562
+ class HTTP2ConnectionByteStream:
563
+ def __init__(
564
+ self, connection: AsyncHTTP2Connection, request: Request, stream_id: int
565
+ ) -> None:
566
+ self._connection = connection
567
+ self._request = request
568
+ self._stream_id = stream_id
569
+ self._closed = False
570
+
571
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
572
+ kwargs = {"request": self._request, "stream_id": self._stream_id}
573
+ try:
574
+ async with Trace("receive_response_body", logger, self._request, kwargs):
575
+ async for chunk in self._connection._receive_response_body(
576
+ request=self._request, stream_id=self._stream_id
577
+ ):
578
+ yield chunk
579
+ except BaseException as exc:
580
+ # If we get an exception while streaming the response,
581
+ # we want to close the response (and possibly the connection)
582
+ # before raising that exception.
583
+ with AsyncShieldCancellation():
584
+ await self.aclose()
585
+ raise exc
586
+
587
+ async def aclose(self) -> None:
588
+ if not self._closed:
589
+ self._closed = True
590
+ kwargs = {"stream_id": self._stream_id}
591
+ async with Trace("response_closed", logger, self._request, kwargs):
592
+ await self._connection._response_closed(stream_id=self._stream_id)
lib/python3.12/site-packages/httpcore/_async/http_proxy.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import logging
5
+ import ssl
6
+ import typing
7
+
8
+ from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
9
+ from .._exceptions import ProxyError
10
+ from .._models import (
11
+ URL,
12
+ Origin,
13
+ Request,
14
+ Response,
15
+ enforce_bytes,
16
+ enforce_headers,
17
+ enforce_url,
18
+ )
19
+ from .._ssl import default_ssl_context
20
+ from .._synchronization import AsyncLock
21
+ from .._trace import Trace
22
+ from .connection import AsyncHTTPConnection
23
+ from .connection_pool import AsyncConnectionPool
24
+ from .http11 import AsyncHTTP11Connection
25
+ from .interfaces import AsyncConnectionInterface
26
+
27
+ ByteOrStr = typing.Union[bytes, str]
28
+ HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
29
+ HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
30
+
31
+
32
+ logger = logging.getLogger("httpcore.proxy")
33
+
34
+
35
+ def merge_headers(
36
+ default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
37
+ override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
38
+ ) -> list[tuple[bytes, bytes]]:
39
+ """
40
+ Append default_headers and override_headers, de-duplicating if a key exists
41
+ in both cases.
42
+ """
43
+ default_headers = [] if default_headers is None else list(default_headers)
44
+ override_headers = [] if override_headers is None else list(override_headers)
45
+ has_override = set(key.lower() for key, value in override_headers)
46
+ default_headers = [
47
+ (key, value)
48
+ for key, value in default_headers
49
+ if key.lower() not in has_override
50
+ ]
51
+ return default_headers + override_headers
52
+
53
+
54
+ class AsyncHTTPProxy(AsyncConnectionPool): # pragma: nocover
55
+ """
56
+ A connection pool that sends requests via an HTTP proxy.
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ proxy_url: URL | bytes | str,
62
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
63
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
64
+ ssl_context: ssl.SSLContext | None = None,
65
+ proxy_ssl_context: ssl.SSLContext | None = None,
66
+ max_connections: int | None = 10,
67
+ max_keepalive_connections: int | None = None,
68
+ keepalive_expiry: float | None = None,
69
+ http1: bool = True,
70
+ http2: bool = False,
71
+ retries: int = 0,
72
+ local_address: str | None = None,
73
+ uds: str | None = None,
74
+ network_backend: AsyncNetworkBackend | None = None,
75
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
76
+ ) -> None:
77
+ """
78
+ A connection pool for making HTTP requests.
79
+
80
+ Parameters:
81
+ proxy_url: The URL to use when connecting to the proxy server.
82
+ For example `"http://127.0.0.1:8080/"`.
83
+ proxy_auth: Any proxy authentication as a two-tuple of
84
+ (username, password). May be either bytes or ascii-only str.
85
+ proxy_headers: Any HTTP headers to use for the proxy requests.
86
+ For example `{"Proxy-Authorization": "Basic <username>:<password>"}`.
87
+ ssl_context: An SSL context to use for verifying connections.
88
+ If not specified, the default `httpcore.default_ssl_context()`
89
+ will be used.
90
+ proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin.
91
+ max_connections: The maximum number of concurrent HTTP connections that
92
+ the pool should allow. Any attempt to send a request on a pool that
93
+ would exceed this amount will block until a connection is available.
94
+ max_keepalive_connections: The maximum number of idle HTTP connections
95
+ that will be maintained in the pool.
96
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
97
+ may be maintained for before being expired from the pool.
98
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
99
+ by the connection pool. Defaults to True.
100
+ http2: A boolean indicating if HTTP/2 requests should be supported by
101
+ the connection pool. Defaults to False.
102
+ retries: The maximum number of retries when trying to establish
103
+ a connection.
104
+ local_address: Local address to connect from. Can also be used to
105
+ connect using a particular address family. Using
106
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
107
+ (IPv4), while using `local_address="::"` will connect using an
108
+ `AF_INET6` address (IPv6).
109
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
110
+ network_backend: A backend instance to use for handling network I/O.
111
+ """
112
+ super().__init__(
113
+ ssl_context=ssl_context,
114
+ max_connections=max_connections,
115
+ max_keepalive_connections=max_keepalive_connections,
116
+ keepalive_expiry=keepalive_expiry,
117
+ http1=http1,
118
+ http2=http2,
119
+ network_backend=network_backend,
120
+ retries=retries,
121
+ local_address=local_address,
122
+ uds=uds,
123
+ socket_options=socket_options,
124
+ )
125
+
126
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
127
+ if (
128
+ self._proxy_url.scheme == b"http" and proxy_ssl_context is not None
129
+ ): # pragma: no cover
130
+ raise RuntimeError(
131
+ "The `proxy_ssl_context` argument is not allowed for the http scheme"
132
+ )
133
+
134
+ self._ssl_context = ssl_context
135
+ self._proxy_ssl_context = proxy_ssl_context
136
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
137
+ if proxy_auth is not None:
138
+ username = enforce_bytes(proxy_auth[0], name="proxy_auth")
139
+ password = enforce_bytes(proxy_auth[1], name="proxy_auth")
140
+ userpass = username + b":" + password
141
+ authorization = b"Basic " + base64.b64encode(userpass)
142
+ self._proxy_headers = [
143
+ (b"Proxy-Authorization", authorization)
144
+ ] + self._proxy_headers
145
+
146
+ def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
147
+ if origin.scheme == b"http":
148
+ return AsyncForwardHTTPConnection(
149
+ proxy_origin=self._proxy_url.origin,
150
+ proxy_headers=self._proxy_headers,
151
+ remote_origin=origin,
152
+ keepalive_expiry=self._keepalive_expiry,
153
+ network_backend=self._network_backend,
154
+ proxy_ssl_context=self._proxy_ssl_context,
155
+ )
156
+ return AsyncTunnelHTTPConnection(
157
+ proxy_origin=self._proxy_url.origin,
158
+ proxy_headers=self._proxy_headers,
159
+ remote_origin=origin,
160
+ ssl_context=self._ssl_context,
161
+ proxy_ssl_context=self._proxy_ssl_context,
162
+ keepalive_expiry=self._keepalive_expiry,
163
+ http1=self._http1,
164
+ http2=self._http2,
165
+ network_backend=self._network_backend,
166
+ )
167
+
168
+
169
+ class AsyncForwardHTTPConnection(AsyncConnectionInterface):
170
+ def __init__(
171
+ self,
172
+ proxy_origin: Origin,
173
+ remote_origin: Origin,
174
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
175
+ keepalive_expiry: float | None = None,
176
+ network_backend: AsyncNetworkBackend | None = None,
177
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
178
+ proxy_ssl_context: ssl.SSLContext | None = None,
179
+ ) -> None:
180
+ self._connection = AsyncHTTPConnection(
181
+ origin=proxy_origin,
182
+ keepalive_expiry=keepalive_expiry,
183
+ network_backend=network_backend,
184
+ socket_options=socket_options,
185
+ ssl_context=proxy_ssl_context,
186
+ )
187
+ self._proxy_origin = proxy_origin
188
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
189
+ self._remote_origin = remote_origin
190
+
191
+ async def handle_async_request(self, request: Request) -> Response:
192
+ headers = merge_headers(self._proxy_headers, request.headers)
193
+ url = URL(
194
+ scheme=self._proxy_origin.scheme,
195
+ host=self._proxy_origin.host,
196
+ port=self._proxy_origin.port,
197
+ target=bytes(request.url),
198
+ )
199
+ proxy_request = Request(
200
+ method=request.method,
201
+ url=url,
202
+ headers=headers,
203
+ content=request.stream,
204
+ extensions=request.extensions,
205
+ )
206
+ return await self._connection.handle_async_request(proxy_request)
207
+
208
+ def can_handle_request(self, origin: Origin) -> bool:
209
+ return origin == self._remote_origin
210
+
211
+ async def aclose(self) -> None:
212
+ await self._connection.aclose()
213
+
214
+ def info(self) -> str:
215
+ return self._connection.info()
216
+
217
+ def is_available(self) -> bool:
218
+ return self._connection.is_available()
219
+
220
+ def has_expired(self) -> bool:
221
+ return self._connection.has_expired()
222
+
223
+ def is_idle(self) -> bool:
224
+ return self._connection.is_idle()
225
+
226
+ def is_closed(self) -> bool:
227
+ return self._connection.is_closed()
228
+
229
+ def __repr__(self) -> str:
230
+ return f"<{self.__class__.__name__} [{self.info()}]>"
231
+
232
+
233
+ class AsyncTunnelHTTPConnection(AsyncConnectionInterface):
234
+ def __init__(
235
+ self,
236
+ proxy_origin: Origin,
237
+ remote_origin: Origin,
238
+ ssl_context: ssl.SSLContext | None = None,
239
+ proxy_ssl_context: ssl.SSLContext | None = None,
240
+ proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
241
+ keepalive_expiry: float | None = None,
242
+ http1: bool = True,
243
+ http2: bool = False,
244
+ network_backend: AsyncNetworkBackend | None = None,
245
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
246
+ ) -> None:
247
+ self._connection: AsyncConnectionInterface = AsyncHTTPConnection(
248
+ origin=proxy_origin,
249
+ keepalive_expiry=keepalive_expiry,
250
+ network_backend=network_backend,
251
+ socket_options=socket_options,
252
+ ssl_context=proxy_ssl_context,
253
+ )
254
+ self._proxy_origin = proxy_origin
255
+ self._remote_origin = remote_origin
256
+ self._ssl_context = ssl_context
257
+ self._proxy_ssl_context = proxy_ssl_context
258
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
259
+ self._keepalive_expiry = keepalive_expiry
260
+ self._http1 = http1
261
+ self._http2 = http2
262
+ self._connect_lock = AsyncLock()
263
+ self._connected = False
264
+
265
+ async def handle_async_request(self, request: Request) -> Response:
266
+ timeouts = request.extensions.get("timeout", {})
267
+ timeout = timeouts.get("connect", None)
268
+
269
+ async with self._connect_lock:
270
+ if not self._connected:
271
+ target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port)
272
+
273
+ connect_url = URL(
274
+ scheme=self._proxy_origin.scheme,
275
+ host=self._proxy_origin.host,
276
+ port=self._proxy_origin.port,
277
+ target=target,
278
+ )
279
+ connect_headers = merge_headers(
280
+ [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers
281
+ )
282
+ connect_request = Request(
283
+ method=b"CONNECT",
284
+ url=connect_url,
285
+ headers=connect_headers,
286
+ extensions=request.extensions,
287
+ )
288
+ connect_response = await self._connection.handle_async_request(
289
+ connect_request
290
+ )
291
+
292
+ if connect_response.status < 200 or connect_response.status > 299:
293
+ reason_bytes = connect_response.extensions.get("reason_phrase", b"")
294
+ reason_str = reason_bytes.decode("ascii", errors="ignore")
295
+ msg = "%d %s" % (connect_response.status, reason_str)
296
+ await self._connection.aclose()
297
+ raise ProxyError(msg)
298
+
299
+ stream = connect_response.extensions["network_stream"]
300
+
301
+ # Upgrade the stream to SSL
302
+ ssl_context = (
303
+ default_ssl_context()
304
+ if self._ssl_context is None
305
+ else self._ssl_context
306
+ )
307
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
308
+ ssl_context.set_alpn_protocols(alpn_protocols)
309
+
310
+ kwargs = {
311
+ "ssl_context": ssl_context,
312
+ "server_hostname": self._remote_origin.host.decode("ascii"),
313
+ "timeout": timeout,
314
+ }
315
+ async with Trace("start_tls", logger, request, kwargs) as trace:
316
+ stream = await stream.start_tls(**kwargs)
317
+ trace.return_value = stream
318
+
319
+ # Determine if we should be using HTTP/1.1 or HTTP/2
320
+ ssl_object = stream.get_extra_info("ssl_object")
321
+ http2_negotiated = (
322
+ ssl_object is not None
323
+ and ssl_object.selected_alpn_protocol() == "h2"
324
+ )
325
+
326
+ # Create the HTTP/1.1 or HTTP/2 connection
327
+ if http2_negotiated or (self._http2 and not self._http1):
328
+ from .http2 import AsyncHTTP2Connection
329
+
330
+ self._connection = AsyncHTTP2Connection(
331
+ origin=self._remote_origin,
332
+ stream=stream,
333
+ keepalive_expiry=self._keepalive_expiry,
334
+ )
335
+ else:
336
+ self._connection = AsyncHTTP11Connection(
337
+ origin=self._remote_origin,
338
+ stream=stream,
339
+ keepalive_expiry=self._keepalive_expiry,
340
+ )
341
+
342
+ self._connected = True
343
+ return await self._connection.handle_async_request(request)
344
+
345
+ def can_handle_request(self, origin: Origin) -> bool:
346
+ return origin == self._remote_origin
347
+
348
+ async def aclose(self) -> None:
349
+ await self._connection.aclose()
350
+
351
+ def info(self) -> str:
352
+ return self._connection.info()
353
+
354
+ def is_available(self) -> bool:
355
+ return self._connection.is_available()
356
+
357
+ def has_expired(self) -> bool:
358
+ return self._connection.has_expired()
359
+
360
+ def is_idle(self) -> bool:
361
+ return self._connection.is_idle()
362
+
363
+ def is_closed(self) -> bool:
364
+ return self._connection.is_closed()
365
+
366
+ def __repr__(self) -> str:
367
+ return f"<{self.__class__.__name__} [{self.info()}]>"
lib/python3.12/site-packages/httpcore/_async/interfaces.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import typing
5
+
6
+ from .._models import (
7
+ URL,
8
+ Extensions,
9
+ HeaderTypes,
10
+ Origin,
11
+ Request,
12
+ Response,
13
+ enforce_bytes,
14
+ enforce_headers,
15
+ enforce_url,
16
+ include_request_headers,
17
+ )
18
+
19
+
20
+ class AsyncRequestInterface:
21
+ async def request(
22
+ self,
23
+ method: bytes | str,
24
+ url: URL | bytes | str,
25
+ *,
26
+ headers: HeaderTypes = None,
27
+ content: bytes | typing.AsyncIterator[bytes] | None = None,
28
+ extensions: Extensions | None = None,
29
+ ) -> Response:
30
+ # Strict type checking on our parameters.
31
+ method = enforce_bytes(method, name="method")
32
+ url = enforce_url(url, name="url")
33
+ headers = enforce_headers(headers, name="headers")
34
+
35
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
36
+ headers = include_request_headers(headers, url=url, content=content)
37
+
38
+ request = Request(
39
+ method=method,
40
+ url=url,
41
+ headers=headers,
42
+ content=content,
43
+ extensions=extensions,
44
+ )
45
+ response = await self.handle_async_request(request)
46
+ try:
47
+ await response.aread()
48
+ finally:
49
+ await response.aclose()
50
+ return response
51
+
52
+ @contextlib.asynccontextmanager
53
+ async def stream(
54
+ self,
55
+ method: bytes | str,
56
+ url: URL | bytes | str,
57
+ *,
58
+ headers: HeaderTypes = None,
59
+ content: bytes | typing.AsyncIterator[bytes] | None = None,
60
+ extensions: Extensions | None = None,
61
+ ) -> typing.AsyncIterator[Response]:
62
+ # Strict type checking on our parameters.
63
+ method = enforce_bytes(method, name="method")
64
+ url = enforce_url(url, name="url")
65
+ headers = enforce_headers(headers, name="headers")
66
+
67
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
68
+ headers = include_request_headers(headers, url=url, content=content)
69
+
70
+ request = Request(
71
+ method=method,
72
+ url=url,
73
+ headers=headers,
74
+ content=content,
75
+ extensions=extensions,
76
+ )
77
+ response = await self.handle_async_request(request)
78
+ try:
79
+ yield response
80
+ finally:
81
+ await response.aclose()
82
+
83
+ async def handle_async_request(self, request: Request) -> Response:
84
+ raise NotImplementedError() # pragma: nocover
85
+
86
+
87
+ class AsyncConnectionInterface(AsyncRequestInterface):
88
+ async def aclose(self) -> None:
89
+ raise NotImplementedError() # pragma: nocover
90
+
91
+ def info(self) -> str:
92
+ raise NotImplementedError() # pragma: nocover
93
+
94
+ def can_handle_request(self, origin: Origin) -> bool:
95
+ raise NotImplementedError() # pragma: nocover
96
+
97
+ def is_available(self) -> bool:
98
+ """
99
+ Return `True` if the connection is currently able to accept an
100
+ outgoing request.
101
+
102
+ An HTTP/1.1 connection will only be available if it is currently idle.
103
+
104
+ An HTTP/2 connection will be available so long as the stream ID space is
105
+ not yet exhausted, and the connection is not in an error state.
106
+
107
+ While the connection is being established we may not yet know if it is going
108
+ to result in an HTTP/1.1 or HTTP/2 connection. The connection should be
109
+ treated as being available, but might ultimately raise `NewConnectionRequired`
110
+ required exceptions if multiple requests are attempted over a connection
111
+ that ends up being established as HTTP/1.1.
112
+ """
113
+ raise NotImplementedError() # pragma: nocover
114
+
115
+ def has_expired(self) -> bool:
116
+ """
117
+ Return `True` if the connection is in a state where it should be closed.
118
+
119
+ This either means that the connection is idle and it has passed the
120
+ expiry time on its keep-alive, or that server has sent an EOF.
121
+ """
122
+ raise NotImplementedError() # pragma: nocover
123
+
124
+ def is_idle(self) -> bool:
125
+ """
126
+ Return `True` if the connection is currently idle.
127
+ """
128
+ raise NotImplementedError() # pragma: nocover
129
+
130
+ def is_closed(self) -> bool:
131
+ """
132
+ Return `True` if the connection has been closed.
133
+
134
+ Used when a response is closed to determine if the connection may be
135
+ returned to the connection pool or not.
136
+ """
137
+ raise NotImplementedError() # pragma: nocover
lib/python3.12/site-packages/httpcore/_async/socks_proxy.py ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import ssl
5
+
6
+ import socksio
7
+
8
+ from .._backends.auto import AutoBackend
9
+ from .._backends.base import AsyncNetworkBackend, AsyncNetworkStream
10
+ from .._exceptions import ConnectionNotAvailable, ProxyError
11
+ from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
12
+ from .._ssl import default_ssl_context
13
+ from .._synchronization import AsyncLock
14
+ from .._trace import Trace
15
+ from .connection_pool import AsyncConnectionPool
16
+ from .http11 import AsyncHTTP11Connection
17
+ from .interfaces import AsyncConnectionInterface
18
+
19
+ logger = logging.getLogger("httpcore.socks")
20
+
21
+
22
+ AUTH_METHODS = {
23
+ b"\x00": "NO AUTHENTICATION REQUIRED",
24
+ b"\x01": "GSSAPI",
25
+ b"\x02": "USERNAME/PASSWORD",
26
+ b"\xff": "NO ACCEPTABLE METHODS",
27
+ }
28
+
29
+ REPLY_CODES = {
30
+ b"\x00": "Succeeded",
31
+ b"\x01": "General SOCKS server failure",
32
+ b"\x02": "Connection not allowed by ruleset",
33
+ b"\x03": "Network unreachable",
34
+ b"\x04": "Host unreachable",
35
+ b"\x05": "Connection refused",
36
+ b"\x06": "TTL expired",
37
+ b"\x07": "Command not supported",
38
+ b"\x08": "Address type not supported",
39
+ }
40
+
41
+
42
+ async def _init_socks5_connection(
43
+ stream: AsyncNetworkStream,
44
+ *,
45
+ host: bytes,
46
+ port: int,
47
+ auth: tuple[bytes, bytes] | None = None,
48
+ ) -> None:
49
+ conn = socksio.socks5.SOCKS5Connection()
50
+
51
+ # Auth method request
52
+ auth_method = (
53
+ socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED
54
+ if auth is None
55
+ else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD
56
+ )
57
+ conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
58
+ outgoing_bytes = conn.data_to_send()
59
+ await stream.write(outgoing_bytes)
60
+
61
+ # Auth method response
62
+ incoming_bytes = await stream.read(max_bytes=4096)
63
+ response = conn.receive_data(incoming_bytes)
64
+ assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
65
+ if response.method != auth_method:
66
+ requested = AUTH_METHODS.get(auth_method, "UNKNOWN")
67
+ responded = AUTH_METHODS.get(response.method, "UNKNOWN")
68
+ raise ProxyError(
69
+ f"Requested {requested} from proxy server, but got {responded}."
70
+ )
71
+
72
+ if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD:
73
+ # Username/password request
74
+ assert auth is not None
75
+ username, password = auth
76
+ conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
77
+ outgoing_bytes = conn.data_to_send()
78
+ await stream.write(outgoing_bytes)
79
+
80
+ # Username/password response
81
+ incoming_bytes = await stream.read(max_bytes=4096)
82
+ response = conn.receive_data(incoming_bytes)
83
+ assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
84
+ if not response.success:
85
+ raise ProxyError("Invalid username/password")
86
+
87
+ # Connect request
88
+ conn.send(
89
+ socksio.socks5.SOCKS5CommandRequest.from_address(
90
+ socksio.socks5.SOCKS5Command.CONNECT, (host, port)
91
+ )
92
+ )
93
+ outgoing_bytes = conn.data_to_send()
94
+ await stream.write(outgoing_bytes)
95
+
96
+ # Connect response
97
+ incoming_bytes = await stream.read(max_bytes=4096)
98
+ response = conn.receive_data(incoming_bytes)
99
+ assert isinstance(response, socksio.socks5.SOCKS5Reply)
100
+ if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
101
+ reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN")
102
+ raise ProxyError(f"Proxy Server could not connect: {reply_code}.")
103
+
104
+
105
+ class AsyncSOCKSProxy(AsyncConnectionPool): # pragma: nocover
106
+ """
107
+ A connection pool that sends requests via an HTTP proxy.
108
+ """
109
+
110
+ def __init__(
111
+ self,
112
+ proxy_url: URL | bytes | str,
113
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
114
+ ssl_context: ssl.SSLContext | None = None,
115
+ max_connections: int | None = 10,
116
+ max_keepalive_connections: int | None = None,
117
+ keepalive_expiry: float | None = None,
118
+ http1: bool = True,
119
+ http2: bool = False,
120
+ retries: int = 0,
121
+ network_backend: AsyncNetworkBackend | None = None,
122
+ ) -> None:
123
+ """
124
+ A connection pool for making HTTP requests.
125
+
126
+ Parameters:
127
+ proxy_url: The URL to use when connecting to the proxy server.
128
+ For example `"http://127.0.0.1:8080/"`.
129
+ ssl_context: An SSL context to use for verifying connections.
130
+ If not specified, the default `httpcore.default_ssl_context()`
131
+ will be used.
132
+ max_connections: The maximum number of concurrent HTTP connections that
133
+ the pool should allow. Any attempt to send a request on a pool that
134
+ would exceed this amount will block until a connection is available.
135
+ max_keepalive_connections: The maximum number of idle HTTP connections
136
+ that will be maintained in the pool.
137
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
138
+ may be maintained for before being expired from the pool.
139
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
140
+ by the connection pool. Defaults to True.
141
+ http2: A boolean indicating if HTTP/2 requests should be supported by
142
+ the connection pool. Defaults to False.
143
+ retries: The maximum number of retries when trying to establish
144
+ a connection.
145
+ local_address: Local address to connect from. Can also be used to
146
+ connect using a particular address family. Using
147
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
148
+ (IPv4), while using `local_address="::"` will connect using an
149
+ `AF_INET6` address (IPv6).
150
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
151
+ network_backend: A backend instance to use for handling network I/O.
152
+ """
153
+ super().__init__(
154
+ ssl_context=ssl_context,
155
+ max_connections=max_connections,
156
+ max_keepalive_connections=max_keepalive_connections,
157
+ keepalive_expiry=keepalive_expiry,
158
+ http1=http1,
159
+ http2=http2,
160
+ network_backend=network_backend,
161
+ retries=retries,
162
+ )
163
+ self._ssl_context = ssl_context
164
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
165
+ if proxy_auth is not None:
166
+ username, password = proxy_auth
167
+ username_bytes = enforce_bytes(username, name="proxy_auth")
168
+ password_bytes = enforce_bytes(password, name="proxy_auth")
169
+ self._proxy_auth: tuple[bytes, bytes] | None = (
170
+ username_bytes,
171
+ password_bytes,
172
+ )
173
+ else:
174
+ self._proxy_auth = None
175
+
176
+ def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
177
+ return AsyncSocks5Connection(
178
+ proxy_origin=self._proxy_url.origin,
179
+ remote_origin=origin,
180
+ proxy_auth=self._proxy_auth,
181
+ ssl_context=self._ssl_context,
182
+ keepalive_expiry=self._keepalive_expiry,
183
+ http1=self._http1,
184
+ http2=self._http2,
185
+ network_backend=self._network_backend,
186
+ )
187
+
188
+
189
+ class AsyncSocks5Connection(AsyncConnectionInterface):
190
+ def __init__(
191
+ self,
192
+ proxy_origin: Origin,
193
+ remote_origin: Origin,
194
+ proxy_auth: tuple[bytes, bytes] | None = None,
195
+ ssl_context: ssl.SSLContext | None = None,
196
+ keepalive_expiry: float | None = None,
197
+ http1: bool = True,
198
+ http2: bool = False,
199
+ network_backend: AsyncNetworkBackend | None = None,
200
+ ) -> None:
201
+ self._proxy_origin = proxy_origin
202
+ self._remote_origin = remote_origin
203
+ self._proxy_auth = proxy_auth
204
+ self._ssl_context = ssl_context
205
+ self._keepalive_expiry = keepalive_expiry
206
+ self._http1 = http1
207
+ self._http2 = http2
208
+
209
+ self._network_backend: AsyncNetworkBackend = (
210
+ AutoBackend() if network_backend is None else network_backend
211
+ )
212
+ self._connect_lock = AsyncLock()
213
+ self._connection: AsyncConnectionInterface | None = None
214
+ self._connect_failed = False
215
+
216
+ async def handle_async_request(self, request: Request) -> Response:
217
+ timeouts = request.extensions.get("timeout", {})
218
+ sni_hostname = request.extensions.get("sni_hostname", None)
219
+ timeout = timeouts.get("connect", None)
220
+
221
+ async with self._connect_lock:
222
+ if self._connection is None:
223
+ try:
224
+ # Connect to the proxy
225
+ kwargs = {
226
+ "host": self._proxy_origin.host.decode("ascii"),
227
+ "port": self._proxy_origin.port,
228
+ "timeout": timeout,
229
+ }
230
+ async with Trace("connect_tcp", logger, request, kwargs) as trace:
231
+ stream = await self._network_backend.connect_tcp(**kwargs)
232
+ trace.return_value = stream
233
+
234
+ # Connect to the remote host using socks5
235
+ kwargs = {
236
+ "stream": stream,
237
+ "host": self._remote_origin.host.decode("ascii"),
238
+ "port": self._remote_origin.port,
239
+ "auth": self._proxy_auth,
240
+ }
241
+ async with Trace(
242
+ "setup_socks5_connection", logger, request, kwargs
243
+ ) as trace:
244
+ await _init_socks5_connection(**kwargs)
245
+ trace.return_value = stream
246
+
247
+ # Upgrade the stream to SSL
248
+ if self._remote_origin.scheme == b"https":
249
+ ssl_context = (
250
+ default_ssl_context()
251
+ if self._ssl_context is None
252
+ else self._ssl_context
253
+ )
254
+ alpn_protocols = (
255
+ ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
256
+ )
257
+ ssl_context.set_alpn_protocols(alpn_protocols)
258
+
259
+ kwargs = {
260
+ "ssl_context": ssl_context,
261
+ "server_hostname": sni_hostname
262
+ or self._remote_origin.host.decode("ascii"),
263
+ "timeout": timeout,
264
+ }
265
+ async with Trace("start_tls", logger, request, kwargs) as trace:
266
+ stream = await stream.start_tls(**kwargs)
267
+ trace.return_value = stream
268
+
269
+ # Determine if we should be using HTTP/1.1 or HTTP/2
270
+ ssl_object = stream.get_extra_info("ssl_object")
271
+ http2_negotiated = (
272
+ ssl_object is not None
273
+ and ssl_object.selected_alpn_protocol() == "h2"
274
+ )
275
+
276
+ # Create the HTTP/1.1 or HTTP/2 connection
277
+ if http2_negotiated or (
278
+ self._http2 and not self._http1
279
+ ): # pragma: nocover
280
+ from .http2 import AsyncHTTP2Connection
281
+
282
+ self._connection = AsyncHTTP2Connection(
283
+ origin=self._remote_origin,
284
+ stream=stream,
285
+ keepalive_expiry=self._keepalive_expiry,
286
+ )
287
+ else:
288
+ self._connection = AsyncHTTP11Connection(
289
+ origin=self._remote_origin,
290
+ stream=stream,
291
+ keepalive_expiry=self._keepalive_expiry,
292
+ )
293
+ except Exception as exc:
294
+ self._connect_failed = True
295
+ raise exc
296
+ elif not self._connection.is_available(): # pragma: nocover
297
+ raise ConnectionNotAvailable()
298
+
299
+ return await self._connection.handle_async_request(request)
300
+
301
+ def can_handle_request(self, origin: Origin) -> bool:
302
+ return origin == self._remote_origin
303
+
304
+ async def aclose(self) -> None:
305
+ if self._connection is not None:
306
+ await self._connection.aclose()
307
+
308
+ def is_available(self) -> bool:
309
+ if self._connection is None: # pragma: nocover
310
+ # If HTTP/2 support is enabled, and the resulting connection could
311
+ # end up as HTTP/2 then we should indicate the connection as being
312
+ # available to service multiple requests.
313
+ return (
314
+ self._http2
315
+ and (self._remote_origin.scheme == b"https" or not self._http1)
316
+ and not self._connect_failed
317
+ )
318
+ return self._connection.is_available()
319
+
320
+ def has_expired(self) -> bool:
321
+ if self._connection is None: # pragma: nocover
322
+ return self._connect_failed
323
+ return self._connection.has_expired()
324
+
325
+ def is_idle(self) -> bool:
326
+ if self._connection is None: # pragma: nocover
327
+ return self._connect_failed
328
+ return self._connection.is_idle()
329
+
330
+ def is_closed(self) -> bool:
331
+ if self._connection is None: # pragma: nocover
332
+ return self._connect_failed
333
+ return self._connection.is_closed()
334
+
335
+ def info(self) -> str:
336
+ if self._connection is None: # pragma: nocover
337
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
338
+ return self._connection.info()
339
+
340
+ def __repr__(self) -> str:
341
+ return f"<{self.__class__.__name__} [{self.info()}]>"
lib/python3.12/site-packages/httpcore/_backends/__init__.py ADDED
File without changes
lib/python3.12/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (197 Bytes). View file
 
lib/python3.12/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc ADDED
Binary file (8.54 kB). View file
 
lib/python3.12/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc ADDED
Binary file (2.69 kB). View file