diff --git a/Cargo.toml b/Cargo.toml index 9be229a9..a661a2a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ docs = ["dep:pyo3-stub-gen"] [dependencies] tokio = { version = "1.44.1", features = ["sync"] } -pyo3 = { version = "0.24.1", features = [ +pyo3 = { version = "0.24.2", features = [ "indexmap", "multiple-pymethods", "generate-import-lib", @@ -33,7 +33,7 @@ pyo3-async-runtimes = { version = "0.24.0", features = [ "tokio-runtime", "unstable-streams", ] } -pyo3-stub-gen = { version = "0.8.0", optional = true } +pyo3-stub-gen = { version = "0.8.1", optional = true } serde = { version = "1.0.219", features = ["derive"] } mime = "0.3.17" indexmap = { version = "2.8.0", features = ["serde"] } diff --git a/examples/client.py b/examples/client.py index 953f276f..a186ccbb 100644 --- a/examples/client.py +++ b/examples/client.py @@ -20,7 +20,7 @@ async def main(): ), ], ) - resp = await client.get("https://api.ip.sb/ip") + resp = await client.get("https://money-tourism.gr/en") print("Status Code: ", resp.status_code) print("Version: ", resp.version) print("Response URL: ", resp.url) diff --git a/rnet.pyi b/rnet.pyi index 4b741666..514948a7 100644 --- a/rnet.pyi +++ b/rnet.pyi @@ -3,6 +3,7 @@ import builtins import datetime +import ipaddress import typing from enum import Enum, auto @@ -12,8 +13,83 @@ class BlockingClient: """ user_agent: typing.Optional[builtins.str] + r""" + Returns the user agent of the client. + + # Returns + + An optional string containing the user agent of the client. + """ headers: HeaderMap - def __new__(cls, **kwds): ... + r""" + Returns the headers of the client. + + # Returns + + A `HeaderMap` object containing the headers of the client. + """ + def __new__(cls, **kwds) -> BlockingClient: + r""" + Creates a new BlockingClient instance. + + # Arguments + + * `**kwds` - Optional request parameters as a dictionary. + + impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]] + user_agent: typing.Optional[str] + default_headers: typing.Optional[typing.Dict[str, bytes]] + headers_order: typing.Optional[typing.List[str]] + referer: typing.Optional[builtins.bool] + allow_redirects: typing.Optional[builtins.bool] + max_redirects: typing.Optional[builtins.int] + cookie_store: typing.Optional[builtins.bool] + lookup_ip_strategy: typing.Optional[LookupIpStrategy] + timeout: typing.Optional[builtins.int] + connect_timeout: typing.Optional[builtins.int] + read_timeout: typing.Optional[builtins.int] + no_keepalive: typing.Optional[builtins.bool] + tcp_keepalive: typing.Optional[builtins.int] + pool_idle_timeout: typing.Optional[builtins.int] + pool_max_idle_per_host: typing.Optional[builtins.int] + pool_max_size: typing.Optional[builtins.int] + http1_only: typing.Optional[builtins.bool] + http2_only: typing.Optional[builtins.bool] + https_only: typing.Optional[builtins.bool] + tcp_nodelay: typing.Optional[builtins.bool] + http2_max_retry_count: typing.Optional[builtins.int] + verify: Optional[Union[bool, Path]] + tls_info: typing.Optional[builtins.bool] + min_tls_version: typing.Optional[TlsVersion] + max_tls_version: typing.Optional[TlsVersion] + no_proxy: typing.Optional[builtins.bool] + proxies: typing.Optional[builtins.list[Proxy]] + local_address: typing.Optional[typing.Optional[typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address]]] + interface: typing.Optional[builtins.str] + gzip: typing.Optional[builtins.bool] + brotli: typing.Optional[builtins.bool] + deflate: typing.Optional[builtins.bool] + zstd: typing.Optional[builtins.bool] + + # Returns + + A new `BlockingClient` instance. + + # Examples + + ```python + import asyncio + import rnet + + client = rnet.BlockingClient( + user_agent="my-app/0.0.1", + timeout=10, + ) + response = client.get('https://httpbin.org/get') + print(response.text()) + ``` + """ + def get_cookies(self, url: str) -> typing.Optional[typing.Any]: r""" Returns the cookies for the given URL. @@ -26,7 +102,6 @@ class BlockingClient: A list of cookie strings. """ - ... def set_cookie(self, url: str, cookie: Cookie) -> None: r""" @@ -45,7 +120,6 @@ class BlockingClient: client.set_cookie("https://example.com", rnet.Cookie(name="foo", value="bar")) ``` """ - ... def remove_cookie(self, url: str, name: str) -> None: r""" @@ -63,15 +137,25 @@ class BlockingClient: client = rnet.Client(cookie_store=True) client.remove_cookie("https://example.com", "foo") """ - ... def clear_cookies(self) -> None: r""" Clears the cookies for the given URL. """ - ... - def update(self, **kwds) -> None: + def update( + self, + impersonate: typing.Optional[ + typing.Union[Impersonate, ImpersonateOption] + ] = None, + headers: typing.Optional[typing.Union[typing.Dict[str, str], HeaderMap]] = None, + headers_order: typing.Optional[typing.List[str]] = None, + proxies: typing.Optional[typing.List[Proxy]] = None, + local_address: typing.Optional[ + typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address] + ] = None, + interface: typing.Optional[builtins.str] = None, + ) -> None: r""" Updates the client with the given parameters. @@ -98,7 +182,6 @@ class BlockingClient: ) ``` """ - ... def request( self, @@ -153,7 +236,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def websocket(self, url: str, **kwds) -> BlockingWebSocket: r""" @@ -203,7 +285,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def trace(self, url: str, **kwds) -> BlockingResponse: r""" @@ -252,7 +333,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def options(self, url: str, **kwds) -> BlockingResponse: r""" @@ -301,7 +381,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def head(self, url: str, **kwds) -> BlockingResponse: r""" @@ -350,7 +429,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def delete(self, url: str, **kwds) -> BlockingResponse: r""" @@ -399,7 +477,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def patch(self, url: str, **kwds) -> BlockingResponse: r""" @@ -448,7 +525,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def put(self, url: str, **kwds) -> BlockingResponse: r""" @@ -497,7 +573,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def post(self, url: str, **kwds) -> BlockingResponse: r""" @@ -546,7 +621,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... def get(self, url: str, **kwds) -> BlockingResponse: r""" @@ -595,7 +669,6 @@ class BlockingClient: asyncio.run(main()) ``` """ - ... class BlockingResponse: r""" @@ -603,15 +676,85 @@ class BlockingResponse: """ url: builtins.str + r""" + Returns the URL of the response. + + # Returns + + A string representing the URL of the response. + """ ok: builtins.bool + r""" + Returns whether the response is successful. + + # Returns + + A boolean indicating whether the response is successful. + """ status: builtins.int + r""" + Returns the status code as integer of the response. + + # Returns + + An integer representing the HTTP status code. + """ status_code: StatusCode + r""" + Returns the status code of the response. + + # Returns + + A Python object representing the HTTP status code. + """ version: Version + r""" + Returns the HTTP version of the response. + + # Returns + + A `Version` object representing the HTTP version of the response. + """ headers: HeaderMap + r""" + Returns the headers of the response. + + # Returns + + A `HeaderMap` object representing the headers of the response. + """ cookies: builtins.list[Cookie] + r""" + Returns the cookies of the response. + + # Returns + + A Python cookies object representing the cookies of the response. + """ content_length: builtins.int + r""" + Returns the content length of the response. + + # Returns + + An integer representing the content length of the response. + """ remote_addr: typing.Optional[SocketAddr] + r""" + Returns the remote address of the response. + + # Returns + + An `IpAddr` object representing the remote address of the response. + """ encoding: builtins.str + r""" + Encoding to decode with when accessing text. + + # Returns + + A string representing the encoding to decode with when accessing text. + """ def __enter__(self) -> BlockingResponse: ... def __exit__( self, _exc_type: typing.Any, _exc_value: typing.Any, _traceback: typing.Any @@ -624,7 +767,6 @@ class BlockingResponse: A Python object representing the TLS peer certificate of the response. """ - ... def text(self) -> builtins.str: r""" @@ -634,7 +776,6 @@ class BlockingResponse: A Python object representing the text content of the response. """ - ... def text_with_charset(self, encoding: builtins.str) -> builtins.str: r""" @@ -648,7 +789,6 @@ class BlockingResponse: A Python object representing the text content of the response. """ - ... def json(self) -> typing.Dict[str, typing.Any]: r""" @@ -658,7 +798,6 @@ class BlockingResponse: A Python object representing the JSON content of the response. """ - ... def bytes(self) -> typing.Any: r""" @@ -668,7 +807,6 @@ class BlockingResponse: A Python object representing the bytes content of the response. """ - ... def stream(self) -> BlockingStreamer: r""" @@ -678,13 +816,11 @@ class BlockingResponse: A Python object representing the stream content of the response. """ - ... def close(self) -> None: r""" Closes the response connection. """ - ... class BlockingStreamer: r""" @@ -708,12 +844,61 @@ class BlockingWebSocket: """ ok: builtins.bool + r""" + Returns whether the response is successful. + + # Returns + + A boolean indicating whether the response is successful. + """ status: builtins.int + r""" + Returns the status code as integer of the response. + + # Returns + + An integer representing the HTTP status code. + """ status_code: StatusCode + r""" + Returns the status code of the response. + + # Returns + + A Python object representing the HTTP status code. + """ version: Version + r""" + Returns the HTTP version of the response. + + # Returns + + A `Version` object representing the HTTP version of the response. + """ headers: HeaderMap + r""" + Returns the headers of the response. + + # Returns + + A `HeaderMap` object representing the headers of the response. + """ cookies: builtins.list[Cookie] + r""" + Returns the cookies of the response. + + # Returns + + A Python cookies object representing the cookies of the response. + """ remote_addr: typing.Optional[SocketAddr] + r""" + Returns the remote address of the response. + + # Returns + + An `IpAddr` object representing the remote address of the response. + """ def __iter__(self) -> BlockingWebSocket: ... def __next__(self) -> Message: ... def __enter__(self) -> BlockingWebSocket: ... @@ -728,13 +913,11 @@ class BlockingWebSocket: An optional string representing the WebSocket protocol. """ - ... def recv(self) -> typing.Optional[Message]: r""" Receives a message from the WebSocket. """ - ... def send(self, message: Message) -> None: r""" @@ -744,7 +927,6 @@ class BlockingWebSocket: * `message` - The message to send. """ - ... def close( self, @@ -759,7 +941,6 @@ class BlockingWebSocket: * `code` - An optional close code. * `reason` - An optional reason for closing. """ - ... class Client: r""" @@ -767,8 +948,104 @@ class Client: """ user_agent: typing.Optional[builtins.str] + r""" + Returns the user agent of the client. + + # Returns + + An optional string containing the user agent of the client. + + # Examples + + ```python + import rnet + + client = rnet.Client() + user_agent = client.user_agent() + print(user_agent) + ``` + """ headers: HeaderMap - def __new__(cls, **kwds): ... + r""" + Returns the headers of the client. + + # Returns + + A `HeaderMap` object containing the headers of the client. + + # Examples + + ```python + import rnet + + client = rnet.Client() + headers = client.headers() + print(headers) + ``` + """ + def __new__(cls, **kwds) -> Client: + r""" + Creates a new Client instance. + + # Arguments + + * `**kwds` - Optional request parameters as a dictionary. + + impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]] + base_url: typing.Optional[str] + user_agent: typing.Optional[str] + default_headers: typing.Optional[typing.Dict[str, bytes]] + headers_order: typing.Optional[typing.List[str]] + referer: typing.Optional[builtins.bool] + allow_redirects: typing.Optional[builtins.bool] + max_redirects: typing.Optional[builtins.int] + cookie_store: typing.Optional[builtins.bool] + lookup_ip_strategy: typing.Optional[LookupIpStrategy] + timeout: typing.Optional[builtins.int] + connect_timeout: typing.Optional[builtins.int] + read_timeout: typing.Optional[builtins.int] + no_keepalive: typing.Optional[builtins.bool] + tcp_keepalive: typing.Optional[builtins.int] + pool_idle_timeout: typing.Optional[builtins.int] + pool_max_idle_per_host: typing.Optional[builtins.int] + pool_max_size: typing.Optional[builtins.int] + http1_only: typing.Optional[builtins.bool] + http2_only: typing.Optional[builtins.bool] + https_only: typing.Optional[builtins.bool] + tcp_nodelay: typing.Optional[builtins.bool] + http2_max_retry_count: typing.Optional[builtins.int] + verify: Optional[Union[bool, Path]] + tls_info: typing.Optional[builtins.bool] + min_tls_version: typing.Optional[TlsVersion] + max_tls_version: typing.Optional[TlsVersion] + no_proxy: typing.Optional[builtins.bool] + proxies: typing.Optional[builtins.list[Proxy]] + local_address: typing.Optional[typing.Optional[typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address]]] + interface: typing.Optional[builtins.str] + gzip: typing.Optional[builtins.bool] + brotli: typing.Optional[builtins.bool] + deflate: typing.Optional[builtins.bool] + zstd: typing.Optional[builtins.bool] + + # Returns + + A new `Client` instance. + + # Examples + + ```python + import asyncio + import rnet + + client = rnet.Client( + user_agent="my-app/0.0.1", + timeout=10, + ) + response = await client.get('https://httpbin.org/get') + print(response.text) + ``` + """ + def get_cookies(self, url: str) -> typing.Optional[typing.Any]: r""" Returns the cookies for the given URL. @@ -791,7 +1068,6 @@ class Client: print(cookies) ``` """ - ... def set_cookie(self, url: str, cookie: Cookie) -> None: r""" @@ -810,7 +1086,6 @@ class Client: client.set_cookie("https://example.com", rnet.Cookie(name="foo", value="bar")) ``` """ - ... def remove_cookie(self, url: str, name: str) -> None: r""" @@ -828,27 +1103,36 @@ class Client: client = rnet.Client(cookie_store=True) client.remove_cookie("https://example.com", "foo") """ - ... def clear_cookies(self) -> None: r""" Clears the cookies for the given URL. """ - ... - def update(self, **kwds) -> None: + def update( + self, + impersonate: typing.Optional[ + typing.Union[Impersonate, ImpersonateOption] + ] = None, + headers: typing.Optional[typing.Union[typing.Dict[str, str], HeaderMap]] = None, + headers_order: typing.Optional[typing.List[str]] = None, + proxies: typing.Optional[typing.List[Proxy]] = None, + local_address: typing.Optional[ + typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address] + ] = None, + interface: typing.Optional[builtins.str] = None, + ) -> None: r""" Updates the client with the given parameters. # Arguments - * `**kwds` - The parameters to update the client with. - impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]] - headers: typing.Optional[typing.Dict[str, bytes]] - headers_order: typing.Optional[typing.List[str]] - proxies: typing.Optional[builtins.list[Proxy]] - local_address: typing.Optional[typing.Optional[typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address]]] - interface: typing.Optional[builtins.str] + * `impersonate` - The impersonation settings for the request. + * `headers` - The headers to use for the request. + * `headers_order` - The order of the headers to use for the request. + * `proxies` - The proxy to use for the request. + * `local_address` - The local IP address to bind to. + * `interface` - The interface to bind to. # Examples @@ -863,7 +1147,6 @@ class Client: ) ``` """ - ... def request(self, method: Method, url: str, **kwds) -> typing.Any: r""" @@ -913,7 +1196,6 @@ class Client: asyncio.run(main()) ``` """ - ... def websocket(self, url: str, **kwds) -> typing.Any: r""" @@ -963,7 +1245,6 @@ class Client: asyncio.run(main()) ``` """ - ... def trace(self, url: str, **kwds) -> typing.Any: r""" @@ -1012,7 +1293,6 @@ class Client: asyncio.run(main()) ``` """ - ... def options(self, url: str, **kwds) -> typing.Any: r""" @@ -1061,7 +1341,6 @@ class Client: asyncio.run(main()) ``` """ - ... def patch(self, url: str, **kwds) -> typing.Any: r""" @@ -1110,7 +1389,6 @@ class Client: asyncio.run(main()) ``` """ - ... def delete(self, url: str, **kwds) -> typing.Any: r""" @@ -1159,7 +1437,6 @@ class Client: asyncio.run(main()) ``` """ - ... def put(self, url: str, **kwds) -> typing.Any: r""" @@ -1208,7 +1485,6 @@ class Client: asyncio.run(main()) ``` """ - ... def post(self, url: str, **kwds) -> typing.Any: r""" @@ -1257,7 +1533,6 @@ class Client: asyncio.run(main()) ``` """ - ... def head(self, url: str, **kwds) -> typing.Any: r""" @@ -1306,7 +1581,6 @@ class Client: asyncio.run(main()) ``` """ - ... def get(self, url: str, **kwds) -> typing.Any: r""" @@ -1355,7 +1629,6 @@ class Client: asyncio.run(main()) ``` """ - ... class Cookie: r""" @@ -1363,15 +1636,45 @@ class Cookie: """ name: builtins.str + r""" + The name of the cookie. + """ value: builtins.str + r""" + The value of the cookie. + """ http_only: builtins.bool + r""" + Returns true if the 'HttpOnly' directive is enabled. + """ secure: builtins.bool + r""" + Returns true if the 'Secure' directive is enabled. + """ same_site_lax: builtins.bool + r""" + Returns true if 'SameSite' directive is 'Lax'. + """ same_site_strict: builtins.bool + r""" + Returns true if 'SameSite' directive is 'Strict'. + """ path: typing.Optional[builtins.str] + r""" + Returns the path directive of the cookie, if set. + """ domain: typing.Optional[builtins.str] + r""" + Returns the domain directive of the cookie, if set. + """ max_age: typing.Optional[datetime.timedelta] + r""" + Get the Max-Age information. + """ expires: typing.Optional[datetime.datetime] + r""" + The cookie expiration time. + """ def __new__( cls, name: builtins.str, @@ -1383,7 +1686,11 @@ class Cookie: http_only: builtins.bool = False, secure: builtins.bool = False, same_site: typing.Optional[SameSite] = None, - ): ... + ) -> Cookie: + r""" + Create a new cookie. + """ + def __str__(self) -> builtins.str: ... def __repr__(self) -> builtins.str: ... @@ -1392,7 +1699,6 @@ class HeaderMap: A HTTP header map. """ - def __new__(cls, init: typing.Optional[dict]): ... def __getitem__(self, key: str) -> typing.Optional[typing.Any]: ... def __setitem__(self, key: str, value: str) -> None: ... def __delitem__(self, key: str) -> None: ... @@ -1401,17 +1707,16 @@ class HeaderMap: def __iter__(self) -> HeaderMapKeysIter: ... def __str__(self) -> builtins.str: ... def __repr__(self) -> builtins.str: ... + def __new__(cls, init: typing.Optional[dict]) -> HeaderMap: ... def get_all(self, key: str) -> HeaderMapValuesIter: r""" Returns multiple value sequences of key mapping """ - ... def items(self) -> HeaderMapItemsIter: r""" Returns key-value pairs in the order they were added. """ - ... class HeaderMapItemsIter: r""" @@ -1450,8 +1755,38 @@ class ImpersonateOption: impersonate_os: typing.Optional[ImpersonateOS] = None, skip_http2: typing.Optional[builtins.bool] = None, skip_headers: typing.Optional[builtins.bool] = None, - ): ... - ... + ) -> ImpersonateOption: + r""" + Create a new impersonation option instance. + + This class allows you to configure browser/client impersonation settings + including the browser type, operating system, and HTTP protocol options. + + Args: + impersonate (Impersonate): The browser/client type to impersonate + impersonate_os (Optional[ImpersonateOS]): The operating system to impersonate, defaults to None + skip_http2 (Optional[bool]): Whether to disable HTTP/2 support, defaults to False + skip_headers (Optional[bool]): Whether to skip default request headers, defaults to False + + Returns: + ImpersonateOption: A new impersonation option instance + + Examples: + ```python + from rnet import ImpersonateOption, Impersonate, ImpersonateOS + + # Basic Chrome 120 impersonation + option = ImpersonateOption(Impersonate.Chrome120) + + # Firefox 136 on Windows with custom options + option = ImpersonateOption( + impersonate=Impersonate.Firefox136, + impersonate_os=ImpersonateOS.Windows, + skip_http2=False, + skip_headers=True + ) + ``` + """ class Message: r""" @@ -1459,11 +1794,53 @@ class Message: """ data: typing.Optional[typing.Any] + r""" + Returns the data of the message as bytes. + + # Returns + + A byte slice representing the data of the message. + """ text: typing.Optional[builtins.str] + r""" + Returns the text content of the message if it is a text message. + + # Returns + + An optional string representing the text content of the message. + """ binary: typing.Optional[typing.Any] + r""" + Returns the binary data of the message if it is a binary message. + + # Returns + + An optional byte slice representing the binary data of the message. + """ ping: typing.Optional[typing.Any] + r""" + Returns the ping data of the message if it is a ping message. + + # Returns + + An optional byte slice representing the ping data of the message. + """ pong: typing.Optional[typing.Any] + r""" + Returns the pong data of the message if it is a pong message. + + # Returns + + An optional byte slice representing the pong data of the message. + """ close: typing.Optional[tuple[builtins.int, typing.Optional[builtins.str]]] + r""" + Returns the close code and reason of the message if it is a close message. + + # Returns + + An optional tuple containing the close code and reason. + """ def __str__(self) -> builtins.str: ... def __repr__(self) -> builtins.str: ... @staticmethod @@ -1478,7 +1855,6 @@ class Message: A new `Message` instance containing the message. """ - ... @staticmethod def binary_from_json(json: typing.Dict[str, typing.Any]) -> Message: @@ -1492,7 +1868,6 @@ class Message: A new `Message` instance containing the message. """ - ... @staticmethod def from_text(text: str) -> Message: @@ -1507,7 +1882,6 @@ class Message: A new `Message` instance containing the text message. """ - ... @staticmethod def from_binary(data: bytes) -> Message: @@ -1522,7 +1896,6 @@ class Message: A new `Message` instance containing the binary message. """ - ... @staticmethod def from_ping(data: bytes) -> Message: @@ -1537,7 +1910,6 @@ class Message: A new `Message` instance containing the ping message. """ - ... @staticmethod def from_pong(data: bytes) -> Message: @@ -1552,7 +1924,6 @@ class Message: A new `Message` instance containing the pong message. """ - ... @staticmethod def from_close(code: builtins.int, reason: typing.Optional[str] = None) -> Message: @@ -1568,21 +1939,21 @@ class Message: A new `Message` instance containing the close message. """ - ... def json(self) -> typing.Dict[str, typing.Any]: r""" Returns the JSON representation of the message. """ - ... class Multipart: r""" A multipart form for a request. """ - def __new__(cls, *parts): ... - ... + def __new__(cls, *parts) -> Multipart: + r""" + Creates a new multipart form. + """ class Part: r""" @@ -1595,8 +1966,16 @@ class Part: value: typing.Any, filename: typing.Optional[builtins.str] = None, mime: typing.Optional[builtins.str] = None, - ): ... - ... + ) -> Part: + r""" + Creates a new part. + + # Arguments + - `name` - The name of the part. + - `value` - The value of the part, either text, bytes, a file path, or a async or sync stream. + - `filename` - The filename of the part. + - `mime` - The MIME type of the part. + """ class Proxy: r""" @@ -1641,7 +2020,6 @@ class Proxy: proxy = rnet.Proxy.http("http://proxy.example.com") ``` """ - ... @staticmethod def https( @@ -1680,7 +2058,6 @@ class Proxy: proxy = rnet.Proxy.https("https://proxy.example.com") ``` """ - ... @staticmethod def all( @@ -1719,7 +2096,6 @@ class Proxy: proxy = rnet.Proxy.all("https://proxy.example.com") ``` """ - ... class Response: r""" @@ -1750,15 +2126,85 @@ class Response: """ url: builtins.str + r""" + Returns the URL of the response. + + # Returns + + A string representing the URL of the response. + """ ok: builtins.bool + r""" + Returns whether the response is successful. + + # Returns + + A boolean indicating whether the response is successful. + """ status: builtins.int + r""" + Returns the status code as integer of the response. + + # Returns + + An integer representing the HTTP status code. + """ status_code: StatusCode + r""" + Returns the status code of the response. + + # Returns + + A Python object representing the HTTP status code. + """ version: Version + r""" + Returns the HTTP version of the response. + + # Returns + + A `Version` object representing the HTTP version of the response. + """ headers: HeaderMap + r""" + Returns the headers of the response. + + # Returns + + A `HeaderMap` object representing the headers of the response. + """ cookies: builtins.list[Cookie] + r""" + Returns the cookies of the response. + + # Returns + + A Python cookies object representing the cookies of the response. + """ content_length: builtins.int + r""" + Returns the content length of the response. + + # Returns + + An integer representing the content length of the response. + """ remote_addr: typing.Optional[SocketAddr] + r""" + Returns the remote address of the response. + + # Returns + + An `IpAddr` object representing the remote address of the response. + """ encoding: builtins.str + r""" + Encoding to decode with when accessing text. + + # Returns + + A string representing the encoding to decode with when accessing text. + """ def __aenter__(self) -> typing.Any: ... def __aexit__( self, _exc_type: typing.Any, _exc_value: typing.Any, _traceback: typing.Any @@ -1771,7 +2217,6 @@ class Response: A Python object representing the TLS peer certificate of the response. """ - ... def text(self) -> typing.Any: r""" @@ -1781,7 +2226,6 @@ class Response: A Python object representing the text content of the response. """ - ... def text_with_charset(self, encoding: builtins.str) -> typing.Any: r""" @@ -1795,7 +2239,6 @@ class Response: A Python object representing the text content of the response. """ - ... def json(self) -> typing.Any: r""" @@ -1805,7 +2248,6 @@ class Response: A Python object representing the JSON content of the response. """ - ... def bytes(self) -> typing.Any: r""" @@ -1815,7 +2257,6 @@ class Response: A Python object representing the bytes content of the response. """ - ... def stream(self) -> Streamer: r""" @@ -1825,13 +2266,11 @@ class Response: A Python object representing the stream content of the response. """ - ... def close(self) -> None: r""" Closes the response connection. """ - ... class SocketAddr: r""" @@ -1843,13 +2282,11 @@ class SocketAddr: r""" Returns the IP address of the socket address. """ - ... def port(self) -> builtins.int: r""" Returns the port number of the socket address. """ - ... class StatusCode: r""" @@ -1862,37 +2299,31 @@ class StatusCode: r""" Return the status code as an integer. """ - ... def is_informational(self) -> builtins.bool: r""" Check if status is within 100-199. """ - ... def is_success(self) -> builtins.bool: r""" Check if status is within 200-299. """ - ... def is_redirection(self) -> builtins.bool: r""" Check if status is within 300-399. """ - ... def is_client_error(self) -> builtins.bool: r""" Check if status is within 400-499. """ - ... def is_server_error(self) -> builtins.bool: r""" Check if status is within 500-599. """ - ... class Streamer: r""" @@ -1942,12 +2373,61 @@ class WebSocket: """ ok: builtins.bool + r""" + Returns whether the response is successful. + + # Returns + + A boolean indicating whether the response is successful. + """ status: builtins.int + r""" + Returns the status code as integer of the response. + + # Returns + + An integer representing the HTTP status code. + """ status_code: StatusCode + r""" + Returns the status code of the response. + + # Returns + + A Python object representing the HTTP status code. + """ version: Version + r""" + Returns the HTTP version of the response. + + # Returns + + A `Version` object representing the HTTP version of the response. + """ headers: HeaderMap + r""" + Returns the headers of the response. + + # Returns + + A `HeaderMap` object representing the headers of the response. + """ cookies: builtins.list[Cookie] + r""" + Returns the cookies of the response. + + # Returns + + A Python cookies object representing the cookies of the response. + """ remote_addr: typing.Optional[SocketAddr] + r""" + Returns the remote address of the response. + + # Returns + + An `IpAddr` object representing the remote address of the response. + """ def __aiter__(self) -> WebSocket: ... def __anext__(self) -> typing.Any: ... def __aenter__(self) -> typing.Any: ... @@ -1962,13 +2442,11 @@ class WebSocket: An optional string representing the WebSocket protocol. """ - ... def recv(self) -> typing.Any: r""" Receives a message from the WebSocket. """ - ... def send(self, message: Message) -> typing.Any: r""" @@ -1978,7 +2456,6 @@ class WebSocket: * `message` - The message to send. """ - ... def close( self, @@ -1993,7 +2470,6 @@ class WebSocket: * `code` - An optional close code. * `reason` - An optional reason for closing. """ - ... class Impersonate(Enum): r""" @@ -2173,7 +2649,6 @@ def delete(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def get(url: str, **kwds) -> typing.Any: r""" @@ -2217,7 +2692,6 @@ def get(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def head(url: str, **kwds) -> typing.Any: r""" @@ -2260,7 +2734,6 @@ def head(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def options(url: str, **kwds) -> typing.Any: r""" @@ -2303,7 +2776,6 @@ def options(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def patch(url: str, **kwds) -> typing.Any: r""" @@ -2347,7 +2819,6 @@ def patch(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def post(url: str, **kwds) -> typing.Any: r""" @@ -2391,7 +2862,6 @@ def post(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def put(url: str, **kwds) -> typing.Any: r""" @@ -2435,7 +2905,6 @@ def put(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def request(method: Method, url: str, **kwds) -> typing.Any: r""" @@ -2481,7 +2950,6 @@ def request(method: Method, url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def trace(url: str, **kwds) -> typing.Any: r""" @@ -2524,7 +2992,6 @@ def trace(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... def websocket(url: str, **kwds) -> typing.Any: r""" @@ -2570,4 +3037,3 @@ def websocket(url: str, **kwds) -> typing.Any: asyncio.run(run()) ``` """ - ... diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 268397c0..5e8b8284 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -5,7 +5,8 @@ use crate::{ dns, error::Error, typing::{ - Cookie, HeaderMap, Method, SslVerify, TlsVersion, + Cookie, HeaderMap, HeaderMapExtractor, HeadersOrderExtractor, ImpersonateExtractor, + IpAddrExtractor, Method, ProxyListExtractor, SslVerify, TlsVersion, param::{ClientParams, RequestParams, UpdateClientParams, WebSocketParams}, }, }; @@ -13,9 +14,13 @@ use pyo3::{prelude::*, pybacked::PyBackedStr}; use pyo3_async_runtimes::tokio::future_into_py; #[cfg(feature = "docs")] use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; -use rquest::{CertStore, Url, redirect::Policy}; +use rquest::{ + CertStore, Url, + header::{Entry, OccupiedEntry}, + redirect::Policy, +}; +use std::ops::Deref; use std::time::Duration; -use std::{net::IpAddr, ops::Deref}; /// A client for making HTTP requests. #[cfg_attr(feature = "docs", gen_stub_pyclass)] @@ -597,11 +602,10 @@ impl Client { false ); apply_option!( - apply_transformed_option, + apply_if_some_inner, builder, params.local_address, - local_address, - IpAddr::from + local_address ); #[cfg(any( target_os = "android", @@ -762,14 +766,13 @@ impl Client { /// Updates the client with the given parameters. /// /// # Arguments - /// * `**kwds` - The parameters to update the client with. /// - /// impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]] - /// headers: typing.Optional[typing.Dict[str, bytes]] - /// headers_order: typing.Optional[typing.List[str]] - /// proxies: typing.Optional[builtins.list[Proxy]] - /// local_address: typing.Optional[typing.Optional[typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address]]] - /// interface: typing.Optional[builtins.str] + /// * `impersonate` - The impersonation settings for the request. + /// * `headers` - The headers to use for the request. + /// * `headers_order` - The order of the headers to use for the request. + /// * `proxies` - The proxy to use for the request. + /// * `local_address` - The local IP address to bind to. + /// * `interface` - The interface to bind to. /// /// # Examples /// @@ -783,23 +786,67 @@ impl Client { /// proxies=[rnet.Proxy.all("http://proxy.example.com:8080")], /// ) /// ``` - #[pyo3(signature = (**kwds))] - pub fn update(&self, py: Python, mut kwds: Option) -> PyResult<()> { + #[pyo3(signature = ( + impersonate=None, + headers=None, + headers_order=None, + proxies=None, + local_address=None, + interface=None, + ))] + pub fn update( + &self, + py: Python, + impersonate: Option, + headers: Option, + headers_order: Option, + proxies: Option, + local_address: Option, + interface: Option, + ) -> PyResult<()> { py.allow_threads(|| { - let params = kwds.get_or_insert_default(); + // Create a new client with the current configuration. + let mut params = UpdateClientParams { + impersonate, + headers, + headers_order, + proxies, + local_address, + interface, + }; + let mut update = self.0.update(); // Impersonation options. - if let Some(impersonate) = params.impersonate.take() { - update = update.emulation(impersonate.0); - } + apply_option!(apply_if_some_inner, update, params.impersonate, emulation); // Updated headers options. - if let Some(mut updated_headers) = params.headers.take() { - update = update.headers(|default_headers| { - for (name, value) in updated_headers.0.drain() { - if let Some(name) = name { - default_headers.insert(name, value); + if let Some(src) = params.headers.take() { + update = update.headers(|dst| { + // IntoIter of HeaderMap yields (Option, HeaderValue). + // The first time a name is yielded, it will be Some(name), and if + // there are more values with the same name, the next yield will be + // None. + + let mut prev_entry: Option> = None; + for (key, value) in src.0 { + match key { + Some(key) => match dst.entry(key) { + Entry::Occupied(mut e) => { + e.insert(value); + prev_entry = Some(e); + } + Entry::Vacant(e) => { + let e = e.insert_entry(value); + prev_entry = Some(e); + } + }, + None => match prev_entry { + Some(ref mut entry) => { + entry.append(value); + } + None => unreachable!("HeaderMap::into_iter yielded None first"), + }, } } }); @@ -816,11 +863,10 @@ impl Client { // Network options. apply_option!(apply_if_some_inner, update, params.proxies, proxies); apply_option!( - apply_transformed_option, + apply_if_some_inner, update, params.local_address, - local_address, - IpAddr::from + local_address ); #[cfg(any( target_os = "android", diff --git a/src/async_impl/request.rs b/src/async_impl/request.rs index ebd02aad..c0b88ca3 100644 --- a/src/async_impl/request.rs +++ b/src/async_impl/request.rs @@ -9,7 +9,6 @@ use crate::{ use pyo3::PyResult; use rquest::redirect::Policy; use rquest::{Client, header}; -use std::net::IpAddr; use std::time::Duration; /// Executes an HTTP request. @@ -67,11 +66,10 @@ where // Network options. apply_option!(apply_if_some_inner, builder, params.proxy, proxy); apply_option!( - apply_transformed_option, + apply_if_some_inner, builder, params.local_address, - local_address, - IpAddr::from + local_address ); #[cfg(any( target_os = "android", @@ -199,11 +197,10 @@ where // Network options. apply_option!(apply_if_some_inner, builder, params.proxy, proxy); apply_option!( - apply_transformed_option, + apply_if_some_inner, builder, params.local_address, - local_address, - IpAddr::from + local_address ); #[cfg(any( target_os = "android", diff --git a/src/blocking/client.rs b/src/blocking/client.rs index 2cefb560..48f3a583 100644 --- a/src/blocking/client.rs +++ b/src/blocking/client.rs @@ -1,8 +1,11 @@ use super::{BlockingResponse, BlockingWebSocket}; use crate::{ async_impl::{self, execute_request, execute_websocket_request}, - typing::param::{ClientParams, RequestParams, UpdateClientParams, WebSocketParams}, - typing::{Cookie, HeaderMap, Method}, + typing::{ + Cookie, HeaderMap, HeaderMapExtractor, HeadersOrderExtractor, ImpersonateExtractor, + IpAddrExtractor, Method, ProxyListExtractor, + param::{ClientParams, RequestParams, WebSocketParams}, + }, }; use pyo3::{prelude::*, pybacked::PyBackedStr}; #[cfg(feature = "docs")] @@ -520,9 +523,33 @@ impl BlockingClient { /// proxies=[rnet.Proxy.all("http://proxy.example.com:8080")], /// ) /// ``` - #[pyo3(signature = (**kwds))] + #[pyo3(signature = ( + impersonate=None, + headers=None, + headers_order=None, + proxies=None, + local_address=None, + interface=None, + ))] #[inline(always)] - fn update(&self, py: Python, kwds: Option) -> PyResult<()> { - self.0.update(py, kwds) + fn update( + &self, + py: Python, + impersonate: Option, + headers: Option, + headers_order: Option, + proxies: Option, + local_address: Option, + interface: Option, + ) -> PyResult<()> { + self.0.update( + py, + impersonate, + headers, + headers_order, + proxies, + local_address, + interface, + ) } } diff --git a/src/typing/enums.rs b/src/typing/enums.rs index d97de12d..8ffac230 100644 --- a/src/typing/enums.rs +++ b/src/typing/enums.rs @@ -1,72 +1,5 @@ +use crate::define_enum_with_conversion; use pyo3::prelude::*; -#[cfg(feature = "docs")] -use pyo3_stub_gen::derive::gen_stub_pyclass_enum; - -macro_rules! define_enum_with_conversion { - ($(#[$meta:meta])* $enum_type:ident, $ffi_type:ty, $($variant:ident),* $(,)?) => { - define_enum_with_conversion!($(#[$meta])* $enum_type, $ffi_type, $( ($variant, $variant) ),*); - }; - - ($(#[$meta:meta])* const, $enum_type:ident, $ffi_type:ty, $($variant:ident),* $(,)?) => { - define_enum_with_conversion!($(#[$meta])* const, $enum_type, $ffi_type, $( ($variant, $variant) ),*); - }; - - ($(#[$meta:meta])* $enum_type:ident, $ffi_type:ty, $(($rust_variant:ident, $ffi_variant:ident)),* $(,)?) => { - $(#[$meta])* - #[cfg_attr(feature = "docs", gen_stub_pyclass_enum)] - #[pyclass(eq, eq_int)] - #[derive(Clone, Copy, PartialEq, Eq, Hash)] - #[allow(non_camel_case_types)] - #[allow(clippy::upper_case_acronyms)] - pub enum $enum_type { - $($rust_variant),* - } - - impl $enum_type { - pub fn into_ffi(self) -> $ffi_type { - match self { - $(<$enum_type>::$rust_variant => <$ffi_type>::$ffi_variant,)* - } - } - - pub fn from_ffi(ffi: $ffi_type) -> Self { - #[allow(unreachable_patterns)] - match ffi { - $(<$ffi_type>::$ffi_variant => <$enum_type>::$rust_variant,)* - _ => unreachable!(), - } - } - } - }; - - ($(#[$meta:meta])* const, $enum_type:ident, $ffi_type:ty, $(($rust_variant:ident, $ffi_variant:ident)),* $(,)?) => { - $(#[$meta])* - #[cfg_attr(feature = "docs", gen_stub_pyclass_enum)] - #[pyclass(eq, eq_int)] - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] - #[allow(non_camel_case_types)] - #[allow(clippy::upper_case_acronyms)] - pub enum $enum_type { - $($rust_variant),* - } - - impl $enum_type { - pub const fn into_ffi(self) -> $ffi_type { - match self { - $(<$enum_type>::$rust_variant => <$ffi_type>::$ffi_variant,)* - } - } - - pub const fn from_ffi(ffi: $ffi_type) -> Self { - #[allow(unreachable_patterns)] - match ffi { - $(<$ffi_type>::$ffi_variant => <$enum_type>::$rust_variant,)* - _ => unreachable!(), - } - } - } - }; -} define_enum_with_conversion!( /// An HTTP version. diff --git a/src/typing/headers.rs b/src/typing/headers.rs index 89687f78..7135dd8c 100644 --- a/src/typing/headers.rs +++ b/src/typing/headers.rs @@ -1,5 +1,6 @@ use crate::{ buffer::{HeaderNameBuffer, HeaderValueBuffer, PyBufferProtocol}, + define_into_pyobject_todo, define_py_stub_gen, error::Error, }; use pyo3::{ @@ -8,10 +9,7 @@ use pyo3::{ types::{PyDict, PyList}, }; #[cfg(feature = "docs")] -use pyo3_stub_gen::{ - PyStubType, TypeInfo, - derive::{gen_stub_pyclass, gen_stub_pymethods}, -}; +use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use rquest::header::{self, HeaderName, HeaderValue}; /// A HTTP header map. @@ -226,29 +224,6 @@ impl FromPyObject<'_> for HeaderMapExtractor { } } -#[cfg(feature = "docs")] -impl<'py> IntoPyObject<'py> for HeaderMapExtractor { - type Target = HeaderMap; - - type Output = Bound<'py, Self::Target>; - - type Error = PyErr; - - fn into_pyobject(self, py: Python<'py>) -> Result { - HeaderMap(self.0).into_pyobject(py) - } -} - -#[cfg(feature = "docs")] -impl PyStubType for HeaderMapExtractor { - fn type_output() -> TypeInfo { - TypeInfo::with_module( - "typing.Union[typing.Dict[str, str], HeaderMap]", - "typing".into(), - ) - } -} - impl<'py> FromPyObject<'py> for HeadersOrderExtractor { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let list = ob.downcast::()?; @@ -262,3 +237,15 @@ impl<'py> FromPyObject<'py> for HeadersOrderExtractor { .map(Self) } } + +define_into_pyobject_todo!(HeaderMapExtractor); + +define_into_pyobject_todo!(HeadersOrderExtractor); + +define_py_stub_gen!( + HeaderMapExtractor, + "typing.Union[typing.Dict[str, str], HeaderMap]", + "typing" +); + +define_py_stub_gen!(HeadersOrderExtractor, "typing.List[str]", "typing"); diff --git a/src/typing/ipaddr.rs b/src/typing/ipaddr.rs index 94a61bb3..7c8c95f4 100644 --- a/src/typing/ipaddr.rs +++ b/src/typing/ipaddr.rs @@ -1,35 +1,25 @@ +use crate::{define_into_pyobject_todo, define_py_stub_gen}; use pyo3::{IntoPyObjectExt, prelude::*}; #[cfg(feature = "docs")] -use pyo3_stub_gen::{ - PyStubType, TypeInfo, - derive::{gen_stub_pyclass, gen_stub_pymethods}, -}; +use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; /// An IP address. #[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct IpAddr(std::net::IpAddr); +pub struct IpAddrExtractor(pub std::net::IpAddr); -impl From for std::net::IpAddr { - fn from(ip: IpAddr) -> Self { - ip.0 - } -} - -impl FromPyObject<'_> for IpAddr { +impl FromPyObject<'_> for IpAddrExtractor { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { - ob.extract().map(IpAddr) + ob.extract().map(IpAddrExtractor) } } -#[cfg(feature = "docs")] -impl PyStubType for IpAddr { - fn type_output() -> TypeInfo { - TypeInfo::with_module( - "typing.Optional[typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address]]", - "ipaddress".into(), - ) - } -} +define_into_pyobject_todo!(IpAddrExtractor); + +define_py_stub_gen!( + IpAddrExtractor, + "typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address]", + "ipaddress" +); /// A IP socket address. #[cfg_attr(feature = "docs", gen_stub_pyclass)] diff --git a/src/typing/macros.rs b/src/typing/macros.rs new file mode 100644 index 00000000..5bf66eaa --- /dev/null +++ b/src/typing/macros.rs @@ -0,0 +1,103 @@ +#[macro_export] +macro_rules! define_enum_with_conversion { + ($(#[$meta:meta])* $enum_type:ident, $ffi_type:ty, $($variant:ident),* $(,)?) => { + define_enum_with_conversion!($(#[$meta])* $enum_type, $ffi_type, $( ($variant, $variant) ),*); + }; + + ($(#[$meta:meta])* const, $enum_type:ident, $ffi_type:ty, $($variant:ident),* $(,)?) => { + define_enum_with_conversion!($(#[$meta])* const, $enum_type, $ffi_type, $( ($variant, $variant) ),*); + }; + + ($(#[$meta:meta])* $enum_type:ident, $ffi_type:ty, $(($rust_variant:ident, $ffi_variant:ident)),* $(,)?) => { + $(#[$meta])* + #[cfg_attr(feature = "docs", pyo3_stub_gen::derive::gen_stub_pyclass_enum)] + #[pyclass(eq, eq_int)] + #[derive(Clone, Copy, PartialEq, Eq, Hash)] + #[allow(non_camel_case_types)] + #[allow(clippy::upper_case_acronyms)] + pub enum $enum_type { + $($rust_variant),* + } + + impl $enum_type { + pub fn into_ffi(self) -> $ffi_type { + match self { + $(<$enum_type>::$rust_variant => <$ffi_type>::$ffi_variant,)* + } + } + + pub fn from_ffi(ffi: $ffi_type) -> Self { + #[allow(unreachable_patterns)] + match ffi { + $(<$ffi_type>::$ffi_variant => <$enum_type>::$rust_variant,)* + _ => unreachable!(), + } + } + } + }; + + ($(#[$meta:meta])* const, $enum_type:ident, $ffi_type:ty, $(($rust_variant:ident, $ffi_variant:ident)),* $(,)?) => { + $(#[$meta])* + #[cfg_attr(feature = "docs", pyo3_stub_gen::derive::gen_stub_pyclass_enum)] + #[pyclass(eq, eq_int)] + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] + #[allow(non_camel_case_types)] + #[allow(clippy::upper_case_acronyms)] + pub enum $enum_type { + $($rust_variant),* + } + + impl $enum_type { + pub const fn into_ffi(self) -> $ffi_type { + match self { + $(<$enum_type>::$rust_variant => <$ffi_type>::$ffi_variant,)* + } + } + + pub const fn from_ffi(ffi: $ffi_type) -> Self { + #[allow(unreachable_patterns)] + match ffi { + $(<$ffi_type>::$ffi_variant => <$enum_type>::$rust_variant,)* + _ => unreachable!(), + } + } + } + }; +} + +#[macro_export] +macro_rules! define_py_stub_gen { + ($type_name:ty, $type_str:expr, $module:expr) => { + #[cfg(feature = "docs")] + impl pyo3_stub_gen::PyStubType for $type_name { + fn type_output() -> pyo3_stub_gen::TypeInfo { + pyo3_stub_gen::TypeInfo::with_module($type_str, $module.into()) + } + } + }; +} + +#[macro_export] +macro_rules! define_into_pyobject_todo { + ($type_name:ty, $msg:expr) => { + impl<'py> pyo3::conversion::IntoPyObject<'py> for $type_name { + type Target = $type_name; + type Output = pyo3::prelude::Bound<'py, Self::Target>; + type Error = pyo3::PyErr; + + fn into_pyobject(self, _: pyo3::Python<'py>) -> Result { + todo!($msg); + } + } + }; + + ($type_name:ty) => { + define_into_pyobject_todo!( + $type_name, + concat!( + stringify!($type_name), + "::into_pyobject is not implemented yet" + ) + ); + }; +} diff --git a/src/typing/mod.rs b/src/typing/mod.rs index c1d5d06a..0f1b3f48 100644 --- a/src/typing/mod.rs +++ b/src/typing/mod.rs @@ -4,12 +4,15 @@ mod enums; mod headers; mod ipaddr; mod json; +mod macros; mod multipart; pub mod param; mod proxy; mod ssl; mod status; +use crate::{define_into_pyobject_todo, define_py_stub_gen}; + pub use self::{ body::BodyExtractor, cookie::{Cookie, CookieExtractor}, @@ -18,10 +21,10 @@ pub use self::{ HeaderMap, HeaderMapExtractor, HeaderMapItemsIter, HeaderMapKeysIter, HeaderMapValuesIter, HeadersOrderExtractor, }, - ipaddr::{IpAddr, SocketAddr}, + ipaddr::{IpAddrExtractor, SocketAddr}, json::Json, multipart::{Multipart, Part}, - proxy::{Proxy, ProxyExtractor}, + proxy::{Proxy, ProxyExtractor, ProxyListExtractor}, ssl::SslVerify, status::StatusCode, }; @@ -82,6 +85,14 @@ impl FromPyObject<'_> for ImpersonateExtractor { } } +define_into_pyobject_todo!(ImpersonateExtractor); + +define_py_stub_gen!( + ImpersonateExtractor, + "typing.Union[Impersonate, ImpersonateOption]", + "typing" +); + /// A struct to represent the `ImpersonateOption` class. #[cfg_attr(feature = "docs", gen_stub_pyclass)] #[pyclass] diff --git a/src/typing/param/client.rs b/src/typing/param/client.rs index 0c4b33ac..347fa8c5 100644 --- a/src/typing/param/client.rs +++ b/src/typing/param/client.rs @@ -1,8 +1,8 @@ use crate::{ extract_option, typing::{ - HeaderMapExtractor, HeadersOrderExtractor, ImpersonateExtractor, IpAddr, LookupIpStrategy, - SslVerify, TlsVersion, proxy::ProxyListExtractor, + HeaderMapExtractor, HeadersOrderExtractor, ImpersonateExtractor, IpAddrExtractor, + LookupIpStrategy, SslVerify, TlsVersion, proxy::ProxyListExtractor, }, }; use pyo3::{prelude::*, pybacked::PyBackedStr}; @@ -101,7 +101,7 @@ pub struct ClientParams { pub proxies: Option, /// Bind to a local IP Address. - pub local_address: Option, + pub local_address: Option, /// Bind to an interface by `SO_BINDTODEVICE`. pub interface: Option, @@ -121,7 +121,6 @@ pub struct ClientParams { } /// The parameters for updating a client. -#[derive(Default)] pub struct UpdateClientParams { /// The impersonation settings for the request. pub impersonate: Option, @@ -137,7 +136,7 @@ pub struct UpdateClientParams { pub proxies: Option, /// Bind to a local IP Address. - pub local_address: Option, + pub local_address: Option, /// Bind to an interface by `SO_BINDTODEVICE`. pub interface: Option, @@ -188,29 +187,9 @@ impl<'py> FromPyObject<'py> for ClientParams { } } -impl<'py> FromPyObject<'py> for UpdateClientParams { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { - let mut params = Self::default(); - extract_option!(ob, params, impersonate); - extract_option!(ob, params, headers); - extract_option!(ob, params, headers_order); - extract_option!(ob, params, proxies); - extract_option!(ob, params, local_address); - extract_option!(ob, params, interface); - Ok(params) - } -} - #[cfg(feature = "docs")] impl PyStubType for ClientParams { fn type_output() -> TypeInfo { TypeInfo::with_module("typing.Dict[str, typing.Any]", "typing".into()) } } - -#[cfg(feature = "docs")] -impl PyStubType for UpdateClientParams { - fn type_output() -> TypeInfo { - TypeInfo::with_module("typing.Dict[str, typing.Any]", "typing".into()) - } -} diff --git a/src/typing/param/request.rs b/src/typing/param/request.rs index f0402f6a..91a22325 100644 --- a/src/typing/param/request.rs +++ b/src/typing/param/request.rs @@ -1,7 +1,7 @@ use crate::{ extract_option, typing::{ - BodyExtractor, CookieExtractor, HeaderMapExtractor, IpAddr, Json, ProxyExtractor, + BodyExtractor, CookieExtractor, HeaderMapExtractor, IpAddrExtractor, Json, ProxyExtractor, UrlEncodedValuesExtractor, Version, multipart::MultipartExtractor, }, }; @@ -16,7 +16,7 @@ pub struct RequestParams { pub proxy: Option, /// Bind to a local IP Address. - pub local_address: Option, + pub local_address: Option, /// Bind to an interface by `SO_BINDTODEVICE`. pub interface: Option, diff --git a/src/typing/param/ws.rs b/src/typing/param/ws.rs index 6922eb1e..f13e5bfa 100644 --- a/src/typing/param/ws.rs +++ b/src/typing/param/ws.rs @@ -1,7 +1,8 @@ use crate::{ extract_option, typing::{ - CookieExtractor, HeaderMapExtractor, IpAddr, ProxyExtractor, UrlEncodedValuesExtractor, + CookieExtractor, HeaderMapExtractor, IpAddrExtractor, ProxyExtractor, + UrlEncodedValuesExtractor, }, }; use pyo3::{prelude::*, pybacked::PyBackedStr}; @@ -15,7 +16,7 @@ pub struct WebSocketParams { pub proxy: Option, /// Bind to a local IP Address. - pub local_address: Option, + pub local_address: Option, /// Bind to an interface by `SO_BINDTODEVICE`. pub interface: Option, diff --git a/src/typing/proxy.rs b/src/typing/proxy.rs index d1b8b673..970ec38c 100644 --- a/src/typing/proxy.rs +++ b/src/typing/proxy.rs @@ -1,7 +1,7 @@ -use crate::error::Error; +use crate::{define_into_pyobject_todo, define_py_stub_gen, error::Error}; use super::HeaderMapExtractor; -use pyo3::{prelude::*, types::PyList}; +use pyo3::{prelude::*, pybacked::PyBackedStr, types::PyList}; #[cfg(feature = "docs")] use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use rquest::header::HeaderValue; @@ -181,6 +181,14 @@ pub struct ProxyExtractor(pub rquest::Proxy); impl FromPyObject<'_> for ProxyExtractor { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + if let Ok(proxy_str) = ob.extract::() { + let proxy = rquest::Proxy::all(proxy_str.as_ref() as &str) + .map(Self) + .map_err(Error::RquestError)?; + + return Ok(proxy); + } + let proxy = ob.downcast::()?; let proxy = proxy.borrow().0.clone(); Ok(Self(proxy)) @@ -188,6 +196,7 @@ impl FromPyObject<'_> for ProxyExtractor { } pub struct ProxyListExtractor(pub Vec); + impl FromPyObject<'_> for ProxyListExtractor { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { let proxies = ob.downcast::()?; @@ -202,3 +211,11 @@ impl FromPyObject<'_> for ProxyListExtractor { .map(Self) } } + +define_into_pyobject_todo!(ProxyExtractor); + +define_into_pyobject_todo!(ProxyListExtractor); + +define_py_stub_gen!(ProxyExtractor, "typing.Union[Proxy, str]", "typing"); + +define_py_stub_gen!(ProxyListExtractor, "typing.List[Proxy]", "typing");