python-socks 2.8.2__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. python_socks/__init__.py +11 -13
  2. python_socks/_abc.py +35 -22
  3. python_socks/_connectors/abc.py +5 -3
  4. python_socks/_connectors/factory_async.py +8 -8
  5. python_socks/_connectors/factory_sync.py +8 -8
  6. python_socks/_connectors/http_async.py +7 -7
  7. python_socks/_connectors/http_sync.py +7 -7
  8. python_socks/_connectors/socks4_async.py +10 -10
  9. python_socks/_connectors/socks4_sync.py +10 -10
  10. python_socks/_connectors/socks5_async.py +29 -26
  11. python_socks/_connectors/socks5_sync.py +36 -28
  12. python_socks/_errors.py +15 -2
  13. python_socks/_helpers.py +35 -32
  14. python_socks/_protocols/errors.py +10 -1
  15. python_socks/_protocols/http.py +55 -60
  16. python_socks/_protocols/socks4.py +22 -26
  17. python_socks/_protocols/socks5.py +77 -70
  18. python_socks/_types.py +4 -0
  19. python_socks/_version.py +2 -2
  20. python_socks/async_/__init__.py +0 -3
  21. python_socks/async_/anyio/__init__.py +1 -2
  22. python_socks/async_/anyio/_connect.py +6 -4
  23. python_socks/async_/anyio/_proxy.py +85 -92
  24. python_socks/async_/anyio/_resolver.py +10 -3
  25. python_socks/async_/anyio/_stream.py +11 -26
  26. python_socks/async_/anyio/v2/__init__.py +1 -5
  27. python_socks/async_/anyio/v2/_connect.py +7 -4
  28. python_socks/async_/anyio/v2/_proxy.py +36 -26
  29. python_socks/async_/anyio/v2/_stream.py +11 -11
  30. python_socks/async_/asyncio/__init__.py +1 -2
  31. python_socks/async_/asyncio/_connect.py +12 -7
  32. python_socks/async_/asyncio/_proxy.py +48 -49
  33. python_socks/async_/asyncio/_resolver.py +11 -4
  34. python_socks/async_/asyncio/_stream.py +9 -13
  35. python_socks/async_/asyncio/v2/__init__.py +1 -2
  36. python_socks/async_/asyncio/v2/_connect.py +8 -5
  37. python_socks/async_/asyncio/v2/_proxy.py +38 -28
  38. python_socks/async_/asyncio/v2/_stream.py +16 -18
  39. python_socks/async_/trio/__init__.py +1 -1
  40. python_socks/async_/trio/_connect.py +5 -4
  41. python_socks/async_/trio/_proxy.py +46 -48
  42. python_socks/async_/trio/_resolver.py +11 -3
  43. python_socks/async_/trio/_stream.py +9 -9
  44. python_socks/async_/trio/v2/__init__.py +1 -5
  45. python_socks/async_/trio/v2/_connect.py +3 -2
  46. python_socks/async_/trio/v2/_proxy.py +34 -24
  47. python_socks/async_/trio/v2/_stream.py +13 -13
  48. python_socks/sync/__init__.py +1 -3
  49. python_socks/sync/_connect.py +4 -3
  50. python_socks/sync/_proxy.py +42 -43
  51. python_socks/sync/_resolver.py +17 -5
  52. python_socks/sync/_stream.py +9 -9
  53. python_socks/sync/v2/__init__.py +1 -5
  54. python_socks/sync/v2/_connect.py +5 -3
  55. python_socks/sync/v2/_proxy.py +35 -26
  56. python_socks/sync/v2/_ssl_transport.py +3 -0
  57. python_socks/sync/v2/_stream.py +19 -14
  58. python_socks-3.0.0.dist-info/METADATA +346 -0
  59. python_socks-3.0.0.dist-info/RECORD +65 -0
  60. {python_socks-2.8.2.dist-info → python_socks-3.0.0.dist-info}/WHEEL +1 -1
  61. python_socks/async_/_proxy_chain.py +0 -34
  62. python_socks/async_/anyio/_chain.py +0 -42
  63. python_socks/async_/anyio/v2/_chain.py +0 -32
  64. python_socks/async_/asyncio/v2/_chain.py +0 -32
  65. python_socks/async_/curio/__init__.py +0 -4
  66. python_socks/async_/curio/_connect.py +0 -17
  67. python_socks/async_/curio/_proxy.py +0 -132
  68. python_socks/async_/curio/_resolver.py +0 -25
  69. python_socks/async_/curio/_stream.py +0 -32
  70. python_socks/async_/trio/v2/_chain.py +0 -32
  71. python_socks/sync/_chain.py +0 -32
  72. python_socks/sync/v2/_chain.py +0 -26
  73. python_socks-2.8.2.dist-info/METADATA +0 -295
  74. python_socks-2.8.2.dist-info/RECORD +0 -77
  75. {python_socks-2.8.2.dist-info → python_socks-3.0.0.dist-info}/licenses/LICENSE.txt +0 -0
  76. {python_socks-2.8.2.dist-info → python_socks-3.0.0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,15 @@
1
+ # ruff:noqa:TRY004
2
+ from __future__ import annotations
3
+
1
4
  import enum
2
- from functools import singledispatchmethod
3
5
  import ipaddress
4
6
  import socket
5
- from typing import Optional, Type, Union
6
7
  from dataclasses import dataclass, field
8
+ from functools import singledispatchmethod
9
+ from typing import Union, final
7
10
 
8
- from .errors import ReplyError
9
11
  from .._helpers import is_ip_address
12
+ from .errors import ReplyError
10
13
 
11
14
  RSV = NULL = AUTH_GRANTED = 0x00
12
15
  SOCKS_VER = 0x05
@@ -25,13 +28,13 @@ class AddressType(enum.IntEnum):
25
28
  IPV6 = 0x04
26
29
 
27
30
  @classmethod
28
- def from_ip_ver(cls, ver: int):
29
- if ver == 4:
31
+ def from_ip_ver(cls, ver: int) -> AddressType:
32
+ if ver == 4: # noqa: PLR2004
30
33
  return cls.IPV4
31
- if ver == 6:
34
+ if ver == 6: # noqa: PLR2004
32
35
  return cls.IPV6
33
36
 
34
- raise ValueError('Invalid IP version')
37
+ raise ValueError("Invalid IP version")
35
38
 
36
39
 
37
40
  class Command(enum.IntEnum):
@@ -53,25 +56,25 @@ class ReplyCode(enum.IntEnum):
53
56
 
54
57
 
55
58
  ReplyMessages = {
56
- ReplyCode.SUCCEEDED: 'Request granted',
57
- ReplyCode.GENERAL_FAILURE: 'General SOCKS server failure',
58
- ReplyCode.CONNECTION_NOT_ALLOWED: 'Connection not allowed by ruleset',
59
- ReplyCode.NETWORK_UNREACHABLE: 'Network unreachable',
60
- ReplyCode.HOST_UNREACHABLE: 'Host unreachable',
61
- ReplyCode.CONNECTION_REFUSED: 'Connection refused by destination host',
62
- ReplyCode.TTL_EXPIRED: 'TTL expired',
63
- ReplyCode.COMMAND_NOT_SUPPORTED: 'Command not supported or protocol error',
64
- ReplyCode.ADDRESS_TYPE_NOT_SUPPORTED: 'Address type not supported',
59
+ ReplyCode.SUCCEEDED: "Request granted",
60
+ ReplyCode.GENERAL_FAILURE: "General SOCKS server failure",
61
+ ReplyCode.CONNECTION_NOT_ALLOWED: "Connection not allowed by ruleset",
62
+ ReplyCode.NETWORK_UNREACHABLE: "Network unreachable",
63
+ ReplyCode.HOST_UNREACHABLE: "Host unreachable",
64
+ ReplyCode.CONNECTION_REFUSED: "Connection refused by destination host",
65
+ ReplyCode.TTL_EXPIRED: "TTL expired",
66
+ ReplyCode.COMMAND_NOT_SUPPORTED: "Command not supported or protocol error",
67
+ ReplyCode.ADDRESS_TYPE_NOT_SUPPORTED: "Address type not supported",
65
68
  }
66
69
 
67
70
 
68
71
  @dataclass
69
72
  class AuthMethodsRequest:
70
- username: Optional[str]
71
- password: Optional[str]
73
+ username: str | None
74
+ password: str | None
72
75
  methods: bytearray = field(init=False)
73
76
 
74
- def __post_init__(self):
77
+ def __post_init__(self) -> None:
75
78
  methods = bytearray([AuthMethod.ANONYMOUS])
76
79
 
77
80
  if self.username and self.password:
@@ -90,26 +93,26 @@ class AuthMethodReply:
90
93
  ver: int
91
94
  method: AuthMethod
92
95
 
93
- def validate(self, request: AuthMethodsRequest):
96
+ def validate(self, request: AuthMethodsRequest) -> None:
94
97
  if self.method not in request.methods: # pragma: no cover
95
- raise ReplyError(f'Unexpected SOCKS authentication method: {self.method}')
98
+ raise ReplyError(f"Unexpected SOCKS authentication method: {self.method}")
96
99
 
97
100
  @classmethod
98
- def loads(cls, data: bytes) -> 'AuthMethodReply':
101
+ def loads(cls, data: bytes) -> AuthMethodReply:
99
102
  if len(data) != cls.SIZE:
100
- raise ReplyError('Malformed authentication method reply')
103
+ raise ReplyError("Malformed authentication method reply")
101
104
 
102
105
  ver = data[0]
103
106
  if ver != SOCKS_VER: # pragma: no cover
104
- raise ReplyError(f'Unexpected SOCKS version number: {ver}')
107
+ raise ReplyError(f"Unexpected SOCKS version number: {ver}")
105
108
 
106
109
  try:
107
110
  method = AuthMethod(data[1])
108
- except ValueError:
109
- raise ReplyError(f'Invalid authentication method: {data[1]:#02X}')
111
+ except ValueError as e:
112
+ raise ReplyError(f"Invalid authentication method: {data[1]:#02X}") from e
110
113
 
111
114
  if method == AuthMethod.NO_ACCEPTABLE: # pragma: no cover
112
- raise ReplyError('No acceptable authentication methods were offered')
115
+ raise ReplyError("No acceptable authentication methods were offered")
113
116
 
114
117
  return cls(ver=ver, method=method)
115
118
 
@@ -125,9 +128,9 @@ class AuthRequest:
125
128
  data = bytearray()
126
129
  data.append(self.VER)
127
130
  data.append(len(self.username))
128
- data += self.username.encode('ascii')
131
+ data += self.username.encode("ascii")
129
132
  data.append(len(self.password))
130
- data += self.password.encode('ascii')
133
+ data += self.password.encode("ascii")
131
134
  return bytes(data)
132
135
 
133
136
 
@@ -139,17 +142,17 @@ class AuthReply:
139
142
  status: int
140
143
 
141
144
  @classmethod
142
- def loads(cls, data: bytes) -> 'AuthReply':
145
+ def loads(cls, data: bytes) -> AuthReply:
143
146
  if len(data) != cls.SIZE:
144
- raise ReplyError('Malformed auth reply')
147
+ raise ReplyError("Malformed auth reply")
145
148
 
146
149
  ver = data[0]
147
150
  if ver != AuthRequest.VER: # pragma: no cover
148
- raise ReplyError('Invalid authentication response')
151
+ raise ReplyError("Invalid authentication response")
149
152
 
150
153
  status = data[1]
151
154
  if status != AUTH_GRANTED: # pragma: no cover
152
- raise ReplyError('Username and password authentication failure')
155
+ raise ReplyError("Username and password authentication failure")
153
156
 
154
157
  return cls(ver=ver, status=status)
155
158
 
@@ -165,15 +168,15 @@ class ConnectRequest:
165
168
  return bytes(data)
166
169
 
167
170
  def _build_addr_request(self) -> bytes:
168
- port = self.port.to_bytes(2, 'big')
171
+ port = self.port.to_bytes(2, "big")
169
172
 
170
173
  if is_ip_address(self.host):
171
174
  ip = ipaddress.ip_address(self.host)
172
175
  address_type = AddressType.from_ip_ver(ip.version)
173
176
  return bytes([address_type]) + ip.packed + port
174
- else:
177
+ else: # noqa: RET505
175
178
  address_type = AddressType.DOMAIN
176
- host = self.host.encode('idna')
179
+ host = self.host.encode("idna")
177
180
  return bytes([address_type, len(host)]) + host + port
178
181
 
179
182
 
@@ -185,43 +188,43 @@ class ConnectReply:
185
188
  bound_host: str
186
189
  bound_port: int
187
190
 
188
- def validate(self):
191
+ def validate(self) -> None:
189
192
  pass
190
193
 
191
194
  @classmethod
192
- def loads(cls, data: bytes) -> 'ConnectReply':
195
+ def loads(cls, data: bytes) -> ConnectReply: # noqa: C901
193
196
  if not data:
194
- raise ReplyError('Empty connect reply')
197
+ raise ReplyError("Empty connect reply")
195
198
 
196
199
  ver = data[0]
197
200
  if ver != SOCKS_VER: # pragma: no cover
198
- raise ReplyError(f'Unexpected SOCKS version number: {ver:#02X}')
201
+ raise ReplyError(f"Unexpected SOCKS version number: {ver:#02X}")
199
202
 
200
203
  try:
201
204
  reply = ReplyCode(data[1])
202
- except IndexError:
203
- raise ReplyError('Malformed connect reply')
204
- except ValueError:
205
- raise ReplyError(f'Invalid reply code: {data[1]:#02X}')
205
+ except IndexError as e:
206
+ raise ReplyError("Malformed connect reply") from e
207
+ except ValueError as e:
208
+ raise ReplyError(f"Invalid reply code: {data[1]:#02X}") from e
206
209
 
207
210
  if reply != ReplyCode.SUCCEEDED: # pragma: no cover
208
- msg = ReplyMessages.get(reply, 'Unknown error') # type: ignore
211
+ msg = ReplyMessages.get(reply, "Unknown error")
209
212
  raise ReplyError(msg, error_code=reply)
210
213
 
211
214
  try:
212
215
  rsv = data[2]
213
- except IndexError:
214
- raise ReplyError('Malformed connect reply')
216
+ except IndexError as e:
217
+ raise ReplyError("Malformed connect reply") from e
215
218
 
216
219
  if rsv != RSV: # pragma: no cover
217
- raise ReplyError(f'The reserved byte must be {RSV:#02X}')
220
+ raise ReplyError(f"The reserved byte must be {RSV:#02X}")
218
221
 
219
222
  try:
220
223
  addr_type = data[3]
221
224
  bnd_host_data = data[4:-2]
222
225
  bnd_port_data = data[-2:]
223
- except IndexError:
224
- raise ReplyError('Malformed connect reply')
226
+ except IndexError as e:
227
+ raise ReplyError("Malformed connect reply") from e
225
228
 
226
229
  if addr_type == AddressType.IPV4:
227
230
  bnd_host = socket.inet_ntop(socket.AF_INET, bnd_host_data)
@@ -231,9 +234,9 @@ class ConnectReply:
231
234
  # host_len = bnd_host_data[0]
232
235
  bnd_host = bnd_host_data[1:].decode()
233
236
  else: # pragma: no cover
234
- raise ReplyError(f'Invalid address type: {addr_type:#02X}')
237
+ raise ReplyError(f"Invalid address type: {addr_type:#02X}")
235
238
 
236
- bnd_port = int.from_bytes(bnd_port_data, 'big')
239
+ bnd_port = int.from_bytes(bnd_port_data, "big")
237
240
 
238
241
  return cls(
239
242
  ver=ver,
@@ -244,35 +247,42 @@ class ConnectReply:
244
247
  )
245
248
 
246
249
 
250
+ @final
247
251
  class StateServerWaitingForAuthMethods:
248
252
  pass
249
253
 
250
254
 
255
+ @final
251
256
  @dataclass
252
257
  class StateClientSentAuthMethods:
253
258
  data: AuthMethodsRequest
254
259
 
255
260
 
261
+ @final
256
262
  @dataclass
257
263
  class StateServerWaitingForAuth:
258
264
  data: AuthMethodReply
259
265
 
260
266
 
267
+ @final
261
268
  @dataclass
262
269
  class StateClientAuthenticated:
263
- data: Optional[AuthReply] = None
270
+ data: AuthReply | None = None
264
271
 
265
272
 
273
+ @final
266
274
  @dataclass
267
275
  class StateClientSentAuthRequest:
268
276
  data: AuthRequest
269
277
 
270
278
 
279
+ @final
271
280
  @dataclass
272
281
  class StateClientSentConnectRequest:
273
282
  data: ConnectRequest
274
283
 
275
284
 
285
+ @final
276
286
  @dataclass
277
287
  class StateServerConnected:
278
288
  data: ConnectReply
@@ -302,38 +312,38 @@ ConnectionState = Union[
302
312
 
303
313
 
304
314
  class Connection:
305
- def __init__(self):
306
- self._state = StateServerWaitingForAuthMethods()
315
+ def __init__(self) -> None:
316
+ self._state: ConnectionState = StateServerWaitingForAuthMethods()
307
317
 
308
318
  @singledispatchmethod
309
319
  def send(self, request: Request) -> bytes:
310
- raise RuntimeError(f'Invalid request type: {request.__class__}')
320
+ raise RuntimeError(f"Invalid request type: {request.__class__}")
311
321
 
312
322
  @send.register
313
323
  def _send_auth_methods(self, request: AuthMethodsRequest) -> bytes:
314
- if not self._state_is(StateServerWaitingForAuthMethods):
315
- raise RuntimeError('Server is not currently waiting for auth methods')
324
+ if not isinstance(self._state, StateServerWaitingForAuthMethods):
325
+ raise RuntimeError("Server is not currently waiting for auth methods")
316
326
  self._state = StateClientSentAuthMethods(request)
317
327
  return request.dumps()
318
328
 
319
329
  @send.register
320
330
  def _send_auth(self, request: AuthRequest) -> bytes:
321
- if not self._state_is(StateServerWaitingForAuth):
322
- raise RuntimeError('Server is not currently waiting for authentication')
331
+ if not isinstance(self._state, StateServerWaitingForAuth):
332
+ raise RuntimeError("Server is not currently waiting for authentication")
323
333
  self._state = StateClientSentAuthRequest(request)
324
334
  return request.dumps()
325
335
 
326
336
  @send.register
327
337
  def _send_connect(self, request: ConnectRequest) -> bytes:
328
- if not self._state_is(StateClientAuthenticated):
329
- raise RuntimeError('Client is not authenticated')
338
+ if not isinstance(self._state, StateClientAuthenticated):
339
+ raise RuntimeError("Client is not authenticated")
330
340
  self._state = StateClientSentConnectRequest(request)
331
341
  return request.dumps()
332
342
 
333
343
  def receive(self, data: bytes) -> Reply:
334
344
  reply: Reply
335
345
 
336
- if self._state_is(StateClientSentAuthMethods):
346
+ if isinstance(self._state, StateClientSentAuthMethods):
337
347
  reply = AuthMethodReply.loads(data)
338
348
  reply.validate(self._state.data)
339
349
  if reply.method == AuthMethod.USERNAME_PASSWORD:
@@ -342,21 +352,18 @@ class Connection:
342
352
  self._state = StateClientAuthenticated()
343
353
  return reply
344
354
 
345
- if self._state_is(StateClientSentAuthRequest):
355
+ if isinstance(self._state, StateClientSentAuthRequest):
346
356
  reply = AuthReply.loads(data)
347
357
  self._state = StateClientAuthenticated(data=reply)
348
358
  return reply
349
359
 
350
- if self._state_is(StateClientSentConnectRequest):
360
+ if isinstance(self._state, StateClientSentConnectRequest):
351
361
  reply = ConnectReply.loads(data)
352
362
  self._state = StateServerConnected(data=reply)
353
363
  return reply
354
364
 
355
- raise RuntimeError(f'Invalid connection state: {self._state}')
356
-
357
- def _state_is(self, state_cls: Type[ConnectionState]):
358
- return self.state.__class__ is state_cls
365
+ raise RuntimeError(f"Invalid connection state: {self._state}")
359
366
 
360
367
  @property
361
- def state(self):
368
+ def state(self) -> ConnectionState:
362
369
  return self._state
python_socks/_types.py CHANGED
@@ -1,3 +1,4 @@
1
+ import socket
1
2
  from enum import Enum
2
3
 
3
4
 
@@ -5,3 +6,6 @@ class ProxyType(Enum):
5
6
  SOCKS4 = 1
6
7
  SOCKS5 = 2
7
8
  HTTP = 3
9
+
10
+
11
+ ResolvedAddress = tuple[socket.AddressFamily, str]
python_socks/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __title__ = 'python-socks'
2
- __version__ = '2.8.2'
1
+ __title__ = "python-socks"
2
+ __version__ = "3.0.0"
@@ -1,3 +0,0 @@
1
- from ._proxy_chain import ProxyChain
2
-
3
- __all__ = ('ProxyChain',)
@@ -1,4 +1,3 @@
1
1
  from ._proxy import AnyioProxy as Proxy
2
- from ._chain import ProxyChain
3
2
 
4
- __all__ = ('Proxy', 'ProxyChain')
3
+ __all__ = ("Proxy",)
@@ -1,4 +1,7 @@
1
- from typing import Optional
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
2
5
  import anyio
3
6
  import anyio.abc
4
7
 
@@ -6,11 +9,10 @@ import anyio.abc
6
9
  async def connect_tcp(
7
10
  host: str,
8
11
  port: int,
9
- local_host: Optional[str] = None,
12
+ **kwargs: Any,
10
13
  ) -> anyio.abc.SocketStream:
11
-
12
14
  return await anyio.connect_tcp(
13
15
  remote_host=host,
14
16
  remote_port=port,
15
- local_host=local_host,
17
+ **kwargs,
16
18
  )
@@ -1,137 +1,130 @@
1
- import ssl
2
- from typing import Any, Optional
3
- import warnings
1
+ from __future__ import annotations
2
+
3
+ from asyncio import IncompleteReadError
4
+ from typing import Any
4
5
 
5
6
  import anyio
7
+ import anyio.abc
6
8
 
7
- from ..._types import ProxyType
9
+ from ..._connectors.factory_async import create_connector
10
+ from ..._errors import (
11
+ ProxyConnectionError,
12
+ ProxyError,
13
+ ProxyTimeoutError,
14
+ )
8
15
  from ..._helpers import parse_proxy_url
9
- from ..._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError
10
-
16
+ from ..._protocols.errors import ReplyError
17
+ from ..._types import ProxyType
18
+ from ._connect import connect_tcp
11
19
  from ._resolver import Resolver
12
20
  from ._stream import AnyioSocketStream
13
- from ._connect import connect_tcp
14
-
15
- from ..._protocols.errors import ReplyError
16
- from ..._connectors.factory_async import create_connector
17
21
 
18
22
  DEFAULT_TIMEOUT = 60
19
23
 
20
24
 
21
25
  class AnyioProxy:
22
- _stream: Optional[AnyioSocketStream]
23
-
24
26
  def __init__(
25
27
  self,
26
28
  proxy_type: ProxyType,
27
29
  host: str,
28
30
  port: int,
29
- username: Optional[str] = None,
30
- password: Optional[str] = None,
31
- rdns: Optional[bool] = None,
32
- proxy_ssl: Optional[ssl.SSLContext] = None,
33
- ):
31
+ username: str | None = None,
32
+ password: str | None = None,
33
+ rdns: bool | None = None, # noqa: FBT001
34
+ forward: AnyioProxy | None = None,
35
+ ) -> None:
34
36
  self._proxy_type = proxy_type
35
37
  self._proxy_host = host
36
38
  self._proxy_port = port
37
39
  self._password = password
38
40
  self._username = username
39
41
  self._rdns = rdns
42
+ self._forward = forward
40
43
 
41
- self._proxy_ssl = proxy_ssl
42
44
  self._resolver = Resolver()
43
45
 
44
46
  async def connect(
45
47
  self,
46
48
  dest_host: str,
47
49
  dest_port: int,
48
- dest_ssl: Optional[ssl.SSLContext] = None,
49
- timeout: Optional[float] = None,
50
+ timeout: float | None = None,
50
51
  **kwargs: Any,
51
- ) -> AnyioSocketStream:
52
+ ) -> anyio.abc.SocketStream:
52
53
  if timeout is None:
53
54
  timeout = DEFAULT_TIMEOUT
54
55
 
55
- _stream = kwargs.get('_stream')
56
- if _stream is not None:
57
- warnings.warn(
58
- "The '_stream' argument is deprecated and will be removed in the future",
59
- DeprecationWarning,
60
- stacklevel=2,
61
- )
62
-
63
- local_host = kwargs.get('local_host')
64
56
  try:
65
57
  with anyio.fail_after(timeout):
66
- if _stream is None:
67
- try:
68
- _stream = AnyioSocketStream(
69
- await connect_tcp(
70
- host=self._proxy_host,
71
- port=self._proxy_port,
72
- local_host=local_host,
73
- )
74
- )
75
- except OSError as e:
76
- msg = 'Could not connect to proxy {}:{} [{}]'.format(
77
- self._proxy_host,
78
- self._proxy_port,
79
- e.strerror,
80
- )
81
- raise ProxyConnectionError(e.errno, msg) from e
82
-
83
- stream = _stream
84
-
85
- try:
86
- if self._proxy_ssl is not None:
87
- stream = await stream.start_tls(
88
- hostname=self._proxy_host,
89
- ssl_context=self._proxy_ssl,
90
- )
91
-
92
- connector = create_connector(
93
- proxy_type=self._proxy_type,
94
- username=self._username,
95
- password=self._password,
96
- rdns=self._rdns,
97
- resolver=self._resolver,
98
- )
99
- await connector.connect(
100
- stream=stream,
101
- host=dest_host,
102
- port=dest_port,
103
- )
104
-
105
- if dest_ssl is not None:
106
- stream = await stream.start_tls(
107
- hostname=dest_host,
108
- ssl_context=dest_ssl,
109
- )
110
-
111
- return stream
112
- except ReplyError as e:
113
- await stream.close()
114
- raise ProxyError(e, error_code=e.error_code)
115
- except BaseException:
116
- await stream.close()
117
- raise
118
-
58
+ return await self._connect(
59
+ dest_host=dest_host,
60
+ dest_port=dest_port,
61
+ **kwargs,
62
+ )
119
63
  except TimeoutError as e:
120
- raise ProxyTimeoutError(f'Proxy connection timed out: {timeout}') from e
64
+ raise ProxyTimeoutError(f"Proxy connection timed out: {timeout}") from e
65
+
66
+ async def _connect(
67
+ self,
68
+ dest_host: str,
69
+ dest_port: int,
70
+ **kwargs: Any,
71
+ ) -> anyio.abc.SocketStream:
72
+ if self._forward is not None:
73
+ _stream = await self._forward.connect(
74
+ dest_host=self._proxy_host,
75
+ dest_port=self._proxy_port,
76
+ timeout=None,
77
+ **kwargs,
78
+ )
79
+ else:
80
+ try:
81
+ _stream = await connect_tcp(
82
+ host=self._proxy_host,
83
+ port=self._proxy_port,
84
+ **kwargs,
85
+ )
86
+ except OSError as e:
87
+ msg = (
88
+ f"Could not connect to proxy "
89
+ f"{self._proxy_host}:{self._proxy_port} [{e.strerror}]"
90
+ )
91
+ raise ProxyConnectionError(e.errno, msg) from e
92
+
93
+ stream = AnyioSocketStream(_stream)
121
94
 
122
- @property
123
- def proxy_host(self):
124
- return self._proxy_host
95
+ try:
96
+ connector = create_connector(
97
+ proxy_type=self._proxy_type,
98
+ username=self._username,
99
+ password=self._password,
100
+ rdns=self._rdns,
101
+ resolver=self._resolver,
102
+ )
103
+ await connector.connect(
104
+ stream=stream,
105
+ host=dest_host,
106
+ port=dest_port,
107
+ )
125
108
 
126
- @property
127
- def proxy_port(self):
128
- return self._proxy_port
109
+ return _stream
110
+ except ReplyError as e:
111
+ await stream.close()
112
+ raise ProxyError(e, error_code=e.error_code) from e
113
+ except IncompleteReadError as e:
114
+ await stream.close()
115
+ raise ProxyError(e) from e
116
+ except BaseException:
117
+ with anyio.CancelScope(shield=True):
118
+ await stream.close()
119
+ raise
129
120
 
130
121
  @classmethod
131
- def create(cls, *args, **kwargs): # for backward compatibility
122
+ def create(
123
+ cls, *args: Any, **kwargs: Any
124
+ ) -> AnyioProxy: # for backward compatibility
132
125
  return cls(*args, **kwargs)
133
126
 
134
127
  @classmethod
135
- def from_url(cls, url: str, **kwargs) -> 'AnyioProxy':
128
+ def from_url(cls, url: str, **kwargs: Any) -> AnyioProxy:
136
129
  url_args = parse_proxy_url(url)
137
130
  return cls(*url_args, **kwargs)
@@ -1,11 +1,18 @@
1
- import anyio
2
1
  import socket
3
2
 
3
+ import anyio
4
+
4
5
  from ... import _abc as abc
6
+ from ..._types import ResolvedAddress
5
7
 
6
8
 
7
9
  class Resolver(abc.AsyncResolver):
8
- async def resolve(self, host, port=0, family=socket.AF_UNSPEC):
10
+ async def resolve(
11
+ self,
12
+ host: str,
13
+ port: int = 0,
14
+ family: socket.AddressFamily = socket.AF_UNSPEC,
15
+ ) -> ResolvedAddress:
9
16
  infos = await anyio.getaddrinfo(
10
17
  host=host,
11
18
  port=port,
@@ -14,7 +21,7 @@ class Resolver(abc.AsyncResolver):
14
21
  )
15
22
 
16
23
  if not infos: # pragma: no cover
17
- raise OSError('Can`t resolve address {}:{} [{}]'.format(host, port, family))
24
+ raise OSError(f"Can`t resolve address {host}:{port} [{family}]")
18
25
 
19
26
  infos = sorted(infos, key=lambda info: info[0])
20
27