From b426563d624c3881d70878d9e36f3a73991ed5cd Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Mon, 17 Feb 2025 19:36:21 +0800 Subject: [PATCH 1/5] feat: Initial support for websocket --- Cargo.toml | 3 +- examples/ws.py | 51 +++ rnet.pyi | 565 ++++++++++++++++---------- src/client.rs | 68 +++- src/lib.rs | 42 +- src/param/mod.rs | 2 + src/param/websocket.rs | 94 +++++ src/{response.rs => response/http.rs} | 0 src/response/mod.rs | 5 + src/response/ws.rs | 396 ++++++++++++++++++ 10 files changed, 999 insertions(+), 227 deletions(-) create mode 100644 examples/ws.py create mode 100644 src/param/websocket.rs rename src/{response.rs => response/http.rs} (100%) create mode 100644 src/response/mod.rs create mode 100644 src/response/ws.rs diff --git a/Cargo.toml b/Cargo.toml index ac293223..d23b1b78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,8 @@ mime = "0.3.17" indexmap = { version = "2.7.0", features = ["serde"] } cookie = "0.18.0" arc-swap = "1.7.1" -rquest = { version = "2.1.0", features = ["full"] } +rquest = { version = "2.1.0", features = ["full", "websocket"] } +futures-util = { version = "0.3.0", default-features = false } [profile.release] lto = true diff --git a/examples/ws.py b/examples/ws.py new file mode 100644 index 00000000..cc8e2b6e --- /dev/null +++ b/examples/ws.py @@ -0,0 +1,51 @@ +import asyncio +import signal +import rnet +from rnet import Message + + +async def send_message(ws): + for i in range(20): + print(f"Sending: Message {i + 1}") + await ws.send(Message.from_text(f"Message {i + 1}")) + await asyncio.sleep(1) + + +async def receive_message(ws): + while True: + try: + message = await ws.recv() + print("Received: ", message) + if message.data == b"Message 20": + print("Closing connection...") + break + except asyncio.CancelledError: + break + + +async def main(): + resp = await rnet.websocket("wss://echo.websocket.org") + print("Status Code: ", resp.status) + print("Version: ", resp.version) + print("Headers: ", resp.headers.to_dict()) + print("Remote Address: ", resp.remote_addr) + + ws = await resp.into_websocket() + + send_task = asyncio.create_task(send_message(ws)) + receive_task = asyncio.create_task(receive_message(ws)) + + async def close_ws(): + await ws.close() + send_task.cancel() + receive_task.cancel() + + loop = asyncio.get_running_loop() + for sig in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler(sig, lambda: asyncio.create_task(close_ws())) + + await asyncio.gather(send_task, receive_task) + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/rnet.pyi b/rnet.pyi index 4a5c7a78..fe8f9d68 100644 --- a/rnet.pyi +++ b/rnet.pyi @@ -8,281 +8,287 @@ class Client: r""" A client for making HTTP requests. """ - def __new__(cls,**kwds): ... - def get(self, url:builtins.str, **kwds) -> typing.Any: + + def __new__(cls, **kwds): ... + def get(self, url: builtins.str, **kwds) -> typing.Any: r""" Sends a GET request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.get("https://httpbin.org/get") print(await response.text()) - + asyncio.run(main()) ``` """ ... - def post(self, url:builtins.str, **kwds) -> typing.Any: + def post(self, url: builtins.str, **kwds) -> typing.Any: r""" Sends a POST request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.post("://httpbin.org/post", json={"key": "value"}) print(await response.text()) - + asyncio.run(main()) ``` """ ... - def put(self, url:builtins.str, **kwds) -> typing.Any: + def put(self, url: builtins.str, **kwds) -> typing.Any: r""" Sends a PUT request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.put("https://httpbin.org/put", json={"key": "value"}) print(await response.text()) - + asyncio.run(main()) ``` """ ... - def patch(self, url:builtins.str, **kwds) -> typing.Any: + def patch(self, url: builtins.str, **kwds) -> typing.Any: r""" Sends a PATCH request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.patch("https://httpbin.org/patch", json={"key": "value"}) print(await response.text()) - + asyncio.run(main()) ``` """ ... - def delete(self, url:builtins.str, **kwds) -> typing.Any: + def delete(self, url: builtins.str, **kwds) -> typing.Any: r""" Sends a DELETE request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.delete("https://httpbin.org/delete") print(await response.text()) - + asyncio.run(main()) ``` """ ... - def head(self, url:builtins.str, **kwds) -> typing.Any: + def head(self, url: builtins.str, **kwds) -> typing.Any: r""" Sends a HEAD request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.head("https://httpbin.org/head") print(response.status_code) - + asyncio.run(main()) ``` """ ... - def options(self, url:builtins.str, **kwds) -> typing.Any: + def options(self, url: builtins.str, **kwds) -> typing.Any: r""" Sends an OPTIONS request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.options("https://httpbin.org/options") print(response.headers) - + asyncio.run(main()) ``` """ ... - def trace(self, url:builtins.str, **kwds) -> typing.Any: + def trace(self, url: builtins.str, **kwds) -> typing.Any: r""" Sends a TRACE request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.trace("https://httpbin.org/trace") print(response.headers) - + asyncio.run(main()) ``` """ ... - def request(self, method:Method, url:builtins.str, **kwds) -> typing.Any: + def request(self, method: Method, url: builtins.str, **kwds) -> typing.Any: r""" Sends a request with the given method and URL. - + # Arguments - + * `method` - The HTTP method to use. * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio from rnet import Method - + async def main(): client = rnet.Client() response = await client.request(Method.GET, "https://httpbin.org/get") print(await response.text()) - + asyncio.run(main()) ``` """ ... + def websocket(self, url: builtins.str, **kwds) -> typing.Any: + r""" + Sends a WebSocket request. + """ + ... class ClientParams: r""" The parameters for a request. - + # Examples - + ```python import rnet from rnet import Impersonate, Version - + params = rnet.RequestParams( impersonate=Impersonate.Chrome100, default_headers={"Content-Type": "application/json"}, @@ -296,12 +302,13 @@ class ClientParams: http2_only=True, referer=True ) - + response = await rnet.get("https://www.rust-lang.org", **params) body = await response.text() print(body) ``` """ + impersonate: typing.Optional[Impersonate] impersonate_os: typing.Optional[ImpersonateOS] impersonate_skip_http2: typing.Optional[builtins.bool] @@ -335,42 +342,43 @@ class ClientParams: class HeaderMap: r""" A HTTP header map. - + # Examples - + ```python import rnet from rnet import Method - + async def main(): resp = await rnet.request(Method.GET, "https://www.google.com/") print("Headers: ", resp.headers.to_dict()) - + if __name__ == "__main__": import asyncio asyncio.run(main()) ``` """ + def to_dict(self) -> dict: r""" Converts the header map to a Python dictionary. - + # Returns - + A Python dictionary representing the headers. """ ... - def __getitem__(self, key:builtins.str) -> typing.Optional[typing.Any]: + def __getitem__(self, key: builtins.str) -> typing.Optional[typing.Any]: r""" Gets the value of the specified header. - + # Arguments - + * `key` - The name of the header. - + # Returns - + An optional byte slice representing the value of the header. """ ... @@ -381,11 +389,11 @@ class HeaderMap: """ ... - class Impersonate: r""" A impersonate. """ + def __str__(self) -> builtins.str: r""" Returns a string representation of the impersonate. @@ -398,7 +406,6 @@ class Impersonate: """ ... - class ImpersonateOS: def __str__(self) -> builtins.str: r""" @@ -412,97 +419,97 @@ class ImpersonateOS: """ ... - class Method: r""" A HTTP method. """ + ... class Proxy: r""" A proxy server for a request. """ + @staticmethod - def http(url:builtins.str) -> Proxy: + def http(url: builtins.str) -> Proxy: r""" Creates a new HTTP proxy. - + # Arguments - + * `url` - The URL of the proxy server. - + # Returns - + A new `Proxy` instance. - + # Examples - + ```python import rnet - + proxy = rnet.Proxy.http("http://proxy.example.com") ``` """ ... @staticmethod - def https(url:builtins.str) -> Proxy: + def https(url: builtins.str) -> Proxy: r""" Creates a new HTTPS proxy. - + # Arguments - + * `url` - The URL of the proxy server. - + # Returns - + A new `Proxy` instance. - + # Examples - + ```python import rnet - + proxy = rnet.Proxy.https("https://proxy.example.com") ``` """ ... @staticmethod - def all(url:builtins.str) -> Proxy: + def all(url: builtins.str) -> Proxy: r""" Creates a new HTTPS proxy. - + # Arguments - + * `url` - The URL of the proxy server. - + # Returns - + A new `Proxy` instance. - + # Examples - + ```python import rnet - + proxy = rnet.Proxy.all("https://proxy.example.com") ``` """ ... - class RequestParams: r""" The parameters for a request. - + # Examples - + ```python import rnet from rnet import Impersonate, Version - + params = rnet.RequestParams( impersonate=Impersonate.Chrome100, version=Version.HTTP_2, @@ -517,12 +524,13 @@ class RequestParams: http2_only=True, referer=True ) - + response = await rnet.get("https://www.rust-lang.org", **params) body = await response.text() print(body) ``` """ + proxy: typing.Optional[builtins.str] interface: typing.Optional[builtins.str] timeout: typing.Optional[builtins.int] @@ -538,13 +546,13 @@ class RequestParams: class Response: r""" A response from a request. - + # Examples - + ```python import asyncio import rnet - + async def main(): response = await rnet.get("https://www.rust-lang.org") print("Status Code: ", response.status_code) @@ -554,14 +562,15 @@ class Response: print("Content-Length: ", response.content_length) print("Encoding: ", response.encoding) print("Remote Address: ", response.remote_addr) - + text_content = await response.text() print("Text: ", text_content) - + if __name__ == "__main__": asyncio.run(main()) ``` """ + url: builtins.str ok: builtins.bool status: builtins.int @@ -574,9 +583,9 @@ class Response: def peer_certificate(self) -> typing.Optional[builtins.list[builtins.int]]: r""" Returns the TLS peer certificate of the response. - + # Returns - + A Python object representing the TLS peer certificate of the response. """ ... @@ -584,23 +593,23 @@ class Response: def text(self) -> typing.Any: r""" Returns the text content of the response. - + # Returns - + A Python object representing the text content of the response. """ ... - def text_with_charset(self, encoding:builtins.str) -> typing.Any: + def text_with_charset(self, encoding: builtins.str) -> typing.Any: r""" Returns the text content of the response with a specific charset. - + # Arguments - + * `default_encoding` - The default encoding to use if the charset is not specified. - + # Returns - + A Python object representing the text content of the response. """ ... @@ -608,9 +617,9 @@ class Response: def json(self) -> typing.Any: r""" Returns the JSON content of the response. - + # Returns - + A Python object representing the JSON content of the response. """ ... @@ -618,9 +627,9 @@ class Response: def json_str(self) -> typing.Any: r""" Returns the JSON string content of the response. - + # Returns - + A Python object representing the JSON content of the response. """ ... @@ -628,9 +637,9 @@ class Response: def json_str_pretty(self) -> typing.Any: r""" Returns the JSON pretty string content of the response. - + # Returns - + A Python object representing the JSON content of the response. """ ... @@ -638,9 +647,9 @@ class Response: def bytes(self) -> typing.Any: r""" Returns the bytes content of the response. - + # Returns - + A Python object representing the bytes content of the response. """ ... @@ -648,9 +657,9 @@ class Response: def stream(self) -> Streamer: r""" Returns the stream content of the response. - + # Returns - + A Python object representing the stream content of the response. """ ... @@ -661,11 +670,11 @@ class Response: """ ... - class SocketAddr: r""" A IP socket address. """ + def ip(self) -> typing.Any: r""" Returns the IP address of the socket address. @@ -684,11 +693,11 @@ class SocketAddr: """ ... - class StatusCode: r""" HTTP status code. """ + def as_int(self) -> builtins.int: r""" Return the status code as an integer. @@ -731,9 +740,7 @@ class StatusCode: """ ... - def __repr__(self) -> builtins.str: - ... - + def __repr__(self) -> builtins.str: ... class Streamer: r""" @@ -742,14 +749,14 @@ class Streamer: This is used to stream the response content. This is used in the `stream` method of the `Response` class. This is used in an asynchronous for loop in Python. - + # Examples - + ```python import asyncio import rnet from rnet import Method, Impersonate - + async def main(): resp = await rnet.get("https://httpbin.org/stream/20") print("Status Code: ", resp.status_code) @@ -759,24 +766,25 @@ class Streamer: print("Content-Length: ", resp.content_length) print("Encoding: ", resp.encoding) print("Remote Address: ", resp.remote_addr) - + streamer = resp.stream() async for chunk in streamer: print("Chunk: ", chunk) await asyncio.sleep(0.1) - + if __name__ == "__main__": asyncio.run(main()) ``` """ + def __aiter__(self) -> Streamer: r""" Returns the `Streamer` instance itself to be used as an asynchronous iterator. - + This method allows the `Streamer` to be used in an asynchronous for loop in Python. - + # Returns - + The `Streamer` instance itself. """ ... @@ -784,13 +792,13 @@ class Streamer: def __anext__(self) -> typing.Optional[typing.Any]: r""" Returns the next chunk of the response as an asynchronous iterator. - + This method implements the `__anext__` method for the `Streamer` class, allowing it to be used as an asynchronous iterator in Python. It returns the next chunk of the response or raises `PyStopAsyncIteration` if the iterator is exhausted. - + # Returns - + A `PyResult` containing an `Option`. If there is a next chunk, it returns `Some(PyObject)`. If the iterator is exhausted, it raises `PyStopAsyncIteration`. """ @@ -804,11 +812,11 @@ class Streamer: """ ... - class Version: r""" A HTTP version. """ + def __str__(self) -> builtins.str: r""" Returns a string representation of the version. @@ -821,56 +829,149 @@ class Version: """ ... +class WebSocket: ... + +class WebSocketParams: + r""" + The parameters for a WebSocket request. + + # Examples + + ```python + import rnet + from rnet import Impersonate, Version + + params = rnet.WebSocketParams( + proxy="http://proxy.example.com", + local_address="192.168.1.1", + interface="eth0", + headers={"Content-Type": "application/json"}, + auth="Basic dXNlcjpwYXNzd29yZA==", + bearer_auth="Bearer token", + basic_auth=("user", "password"), + query=[("key1", "value1"), ("key2", "value2")] + ) + + async with rnet.websocket("wss://echo.websocket.org") as ws: + await ws.send("Hello, World!") + message = await ws.recv() + print(message) + + asyncio.run(run()) + ``` + """ + + proxy: typing.Optional[builtins.str] + interface: typing.Optional[builtins.str] + auth: typing.Optional[builtins.str] + bearer_auth: typing.Optional[builtins.str] + basic_auth: typing.Optional[tuple[builtins.str, typing.Optional[builtins.str]]] + query: typing.Optional[builtins.list[tuple[builtins.str, builtins.str]]] -def delete(url:builtins.str, **kwds) -> typing.Any: +class WebSocketResponse: + r""" + A response from a request. + + # Examples + + ```python + import asyncio + import rnet + + async def main(): + response = await rnet.get("https://www.rust-lang.org") + print("Status Code: ", response.status_code) + print("Version: ", response.version) + print("Response URL: ", response.url) + print("Headers: ", response.headers.to_dict()) + print("Content-Length: ", response.content_length) + print("Encoding: ", response.encoding) + print("Remote Address: ", response.remote_addr) + + text_content = await response.text() + print("Text: ", text_content) + + if __name__ == "__main__": + asyncio.run(main()) + ``` + """ + + ok: builtins.bool + status: builtins.int + version: Version + headers: HeaderMap + remote_addr: typing.Optional[SocketAddr] + def peer_certificate(self) -> typing.Optional[builtins.list[builtins.int]]: + r""" + Returns the TLS peer certificate of the response. + + # Returns + + A Python object representing the TLS peer certificate of the response. + """ + ... + + def into_websocket(self) -> typing.Any: + r""" + Returns the WebSocket of the response. + """ + ... + + def close(self) -> None: + r""" + Closes the response connection. + """ + ... + +def delete(url: builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `DELETE` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.delete("https://httpbin.org/anything") body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def get(url:builtins.str, **kwds) -> typing.Any: +def get(url: builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `GET` request. - + See also the methods on the [`rquest::Response`](./struct.Response.html) type. - + **NOTE**: This function creates a new internal `Client` on each call, and so should not be used if making many requests. Create a [`Client`](./struct.Client.html) instead. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.get("https://httpbin.org/anything") body = await response.text() print(body) - + asyncio.run(run()) ``` - + # Errors - + This function fails if: - + - native TLS backend cannot be initialized - supplied `Url` cannot be parsed - there was an error while sending request @@ -878,143 +979,165 @@ def get(url:builtins.str, **kwds) -> typing.Any: """ ... -def head(url:builtins.str, **kwds) -> typing.Any: +def head(url: builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `HEAD` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.head("https://httpbin.org/anything") print(response.headers) - + asyncio.run(run()) ``` """ ... -def options(url:builtins.str, **kwds) -> typing.Any: +def options(url: builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make an `OPTIONS` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.options("https://httpbin.org/anything") print(response.headers) - + asyncio.run(run()) ``` """ ... -def patch(url:builtins.str, **kwds) -> typing.Any: +def patch(url: builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `PATCH` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.patch("https://httpbin.org/anything", json={"key": "value"}) body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def post(url:builtins.str, **kwds) -> typing.Any: +def post(url: builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `POST` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.post("https://httpbin.org/anything", json={"key": "value"}) body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def put(url:builtins.str, **kwds) -> typing.Any: +def put(url: builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `PUT` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.put("https://httpbin.org/anything", json={"key": "value"}) body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def request(method:Method, url:builtins.str, **kwds) -> typing.Any: +def request(method: Method, url: builtins.str, **kwds) -> typing.Any: r""" Make a request with the given parameters. - + This function allows you to make a request with the specified parameters encapsulated in a `Request` object. - + # Examples - + ```python import rnet import asyncio from rnet import Method - + async def run(): response = await rnet.request(Method.GET, "https://www.rust-lang.org") body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def trace(url:builtins.str, **kwds) -> typing.Any: +def trace(url: builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `TRACE` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.trace("https://www.rust-lang.org") print(response.headers) - + asyncio.run(run()) ``` """ ... +def websocket(url: builtins.str, **kwds) -> typing.Any: + r""" + Make a WebSocket connection with the given parameters. + + This function allows you to make a WebSocket connection with the specified parameters encapsulated in a `WebSocket` object. + + # Examples + + ```python + import rnet + import asyncio + + async def run(): + async with rnet.websocket("wss://echo.websocket.org") as ws: + await ws.send("Hello, World!") + message = await ws.recv() + print(message) + + asyncio.run(run()) + ``` + """ + ... diff --git a/src/client.rs b/src/client.rs index 181f7742..ce31d1b6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,7 @@ use crate::{ error::{wrap_invali_header_name_error, wrap_rquest_error}, - param::{ClientParams, RequestParams}, - response::Response, + param::{ClientParams, RequestParams, WebSocketParams}, + response::{Response, WebSocketResponse}, types::Method, Result, }; @@ -581,6 +581,18 @@ impl Client { let client = self.0.clone(); pyo3_async_runtimes::tokio::future_into_py(py, execute_request(client, method, url, kwds)) } + + /// Sends a WebSocket request. + #[pyo3(signature = (url, **kwds))] + pub fn websocket<'rt>( + &self, + py: Python<'rt>, + url: String, + kwds: Option, + ) -> PyResult> { + let client = self.0.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, execute_websocket_request(client, url, kwds)) + } } /// Executes an HTTP request. @@ -672,3 +684,55 @@ async fn execute_request( .map(Response::from) .map_err(wrap_rquest_error) } + +/// Executes a WebSocket request. +async fn execute_websocket_request( + client: rquest::Client, + url: String, + mut params: Option, +) -> Result { + let params = params.get_or_insert_default(); + let mut builder = client.websocket(url); + + // The protocols to use for the request. + apply_option!(apply_if_some, builder, params.protocols, protocols); + + // The origin to use for the request. + builder = builder.with_builder(|mut builder| { + // Network options. + apply_option!(apply_if_some, builder, params.proxy, proxy); + apply_option!(apply_if_some, builder, params.local_address, local_address); + rquest::cfg_bindable_device!( + apply_option!(apply_if_some, builder, params.interface, interface); + ); + + // Authentication options. + apply_option!(apply_if_some, builder, params.auth, auth); + + // Bearer authentication options. + apply_option!(apply_if_some, builder, params.bearer_auth, bearer_auth); + + // Basic authentication options. + if let Some(basic_auth) = params.basic_auth.take() { + builder = builder.basic_auth(basic_auth.0, basic_auth.1); + } + + // Headers options. + if let Some(headers) = params.headers.take() { + for (key, value) in headers { + builder = builder.header(key, value); + } + } + + // Query options. + apply_option!(apply_if_some_ref, builder, params.query, query); + + builder + }); + + builder + .send() + .await + .map(WebSocketResponse::from) + .map_err(wrap_rquest_error) +} diff --git a/src/lib.rs b/src/lib.rs index acf5f776..1262c969 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,10 @@ mod response; mod types; use client::Client; -use param::{ClientParams, RequestParams}; +use param::{ClientParams, RequestParams, WebSocketParams}; use pyo3::prelude::*; use pyo3_stub_gen::{define_stub_info_gatherer, derive::*}; -use response::{Response, Streamer}; +use response::{Message, Response, Streamer, WebSocket, WebSocketResponse}; use types::{ HeaderMap, Impersonate, ImpersonateOS, Method, Proxy, SocketAddr, StatusCode, Version, }; @@ -243,6 +243,36 @@ fn request( Client::default().request(py, method, url, kwds) } +/// Make a WebSocket connection with the given parameters. +/// +/// This function allows you to make a WebSocket connection with the specified parameters encapsulated in a `WebSocket` object. +/// +/// # Examples +/// +/// ```python +/// import rnet +/// import asyncio +/// +/// async def run(): +/// async with rnet.websocket("wss://echo.websocket.org") as ws: +/// await ws.send("Hello, World!") +/// message = await ws.recv() +/// print(message) +/// +/// asyncio.run(run()) +/// ``` +#[gen_stub_pyfunction] +#[pyfunction] +#[pyo3(signature = (url, **kwds))] +#[inline(always)] +fn websocket( + py: Python<'_>, + url: String, + kwds: Option, +) -> PyResult> { + Client::default().websocket(py, url, kwds) +} + #[pymodule(gil_used = false)] fn rnet(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; @@ -254,11 +284,15 @@ fn rnet(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; - m.add_function(wrap_pyfunction!(request, m)?)?; + m.add_function(wrap_pyfunction!(get, m)?)?; m.add_function(wrap_pyfunction!(post, m)?)?; m.add_function(wrap_pyfunction!(put, m)?)?; @@ -267,6 +301,8 @@ fn rnet(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(head, m)?)?; m.add_function(wrap_pyfunction!(options, m)?)?; m.add_function(wrap_pyfunction!(trace, m)?)?; + m.add_function(wrap_pyfunction!(request, m)?)?; + m.add_function(wrap_pyfunction!(websocket, m)?)?; Ok(()) } diff --git a/src/param/mod.rs b/src/param/mod.rs index 934c5975..04a734ee 100644 --- a/src/param/mod.rs +++ b/src/param/mod.rs @@ -1,5 +1,7 @@ mod client; mod request; +mod websocket; pub use self::client::ClientParams; pub use self::request::RequestParams; +pub use self::websocket::WebSocketParams; diff --git a/src/param/websocket.rs b/src/param/websocket.rs new file mode 100644 index 00000000..335cb466 --- /dev/null +++ b/src/param/websocket.rs @@ -0,0 +1,94 @@ +use std::net::IpAddr; + +use indexmap::IndexMap; +use pyo3::prelude::*; +use pyo3_stub_gen::derive::gen_stub_pyclass; + +/// The parameters for a WebSocket request. +/// +/// # Examples +/// +/// ```python +/// import rnet +/// from rnet import Impersonate, Version +/// +/// params = rnet.WebSocketParams( +/// proxy="http://proxy.example.com", +/// local_address="192.168.1.1", +/// interface="eth0", +/// headers={"Content-Type": "application/json"}, +/// auth="Basic dXNlcjpwYXNzd29yZA==", +/// bearer_auth="Bearer token", +/// basic_auth=("user", "password"), +/// query=[("key1", "value1"), ("key2", "value2")] +/// ) +/// +/// async with rnet.websocket("wss://echo.websocket.org") as ws: +/// await ws.send("Hello, World!") +/// message = await ws.recv() +/// print(message) +/// +/// asyncio.run(run()) +/// ``` +#[gen_stub_pyclass] +#[pyclass] +#[derive(Default, Debug)] +pub struct WebSocketParams { + /// The proxy to use for the request. + #[pyo3(get)] + pub proxy: Option, + + /// Bind to a local IP Address. + pub local_address: Option, + + /// Bind to an interface by `SO_BINDTODEVICE`. + #[pyo3(get)] + pub interface: Option, + + /// The headers to use for the request. + pub headers: Option>, + + /// The protocols to use for the request. + pub protocols: Option>, + + /// The authentication to use for the request. + #[pyo3(get)] + pub auth: Option, + + /// The bearer authentication to use for the request. + #[pyo3(get)] + pub bearer_auth: Option, + + /// The basic authentication to use for the request. + #[pyo3(get)] + pub basic_auth: Option<(String, Option)>, + + /// The query parameters to use for the request. + #[pyo3(get)] + pub query: Option>, +} + +macro_rules! extract_option { + ($ob:expr, $params:expr, $field:ident) => { + if let Ok(value) = $ob.get_item(stringify!($field)) { + $params.$field = value.extract()?; + } + }; +} + +impl<'py> FromPyObject<'py> for WebSocketParams { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + let mut params = Self::default(); + extract_option!(ob, params, proxy); + extract_option!(ob, params, local_address); + extract_option!(ob, params, interface); + + extract_option!(ob, params, headers); + extract_option!(ob, params, protocols); + extract_option!(ob, params, auth); + extract_option!(ob, params, bearer_auth); + extract_option!(ob, params, basic_auth); + extract_option!(ob, params, query); + Ok(params) + } +} diff --git a/src/response.rs b/src/response/http.rs similarity index 100% rename from src/response.rs rename to src/response/http.rs diff --git a/src/response/mod.rs b/src/response/mod.rs new file mode 100644 index 00000000..fc1051f2 --- /dev/null +++ b/src/response/mod.rs @@ -0,0 +1,5 @@ +mod http; +mod ws; + +pub use http::{Response, Streamer}; +pub use ws::{Message, WebSocket, WebSocketResponse}; diff --git a/src/response/ws.rs b/src/response/ws.rs new file mode 100644 index 00000000..39e35bd1 --- /dev/null +++ b/src/response/ws.rs @@ -0,0 +1,396 @@ +use std::sync::Arc; + +use crate::{ + error::{memory_error, wrap_rquest_error}, + types::{HeaderMap, Json, SocketAddr, StatusCode, Version}, +}; +use arc_swap::ArcSwapOption; +use futures_util::{ + stream::{SplitSink, SplitStream}, + SinkExt, StreamExt, TryStreamExt, +}; +use pyo3::{prelude::*, IntoPyObjectExt}; +use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; +use rquest::TlsInfo; +use tokio::sync::Mutex; + +/// A response from a request. +/// +/// # Examples +/// +/// ```python +/// import asyncio +/// import rnet +/// +/// async def main(): +/// response = await rnet.get("https://www.rust-lang.org") +/// print("Status Code: ", response.status_code) +/// print("Version: ", response.version) +/// print("Response URL: ", response.url) +/// print("Headers: ", response.headers.to_dict()) +/// print("Content-Length: ", response.content_length) +/// print("Encoding: ", response.encoding) +/// print("Remote Address: ", response.remote_addr) +/// +/// text_content = await response.text() +/// print("Text: ", text_content) +/// +/// if __name__ == "__main__": +/// asyncio.run(main()) +/// ``` +#[gen_stub_pyclass] +#[pyclass] +pub struct WebSocketResponse { + version: Version, + status_code: StatusCode, + remote_addr: Option, + headers: HeaderMap, + response: ArcSwapOption, +} + +impl From for WebSocketResponse { + fn from(response: rquest::WebSocketResponse) -> Self { + WebSocketResponse { + version: Version::from(response.version()), + status_code: StatusCode::from(response.status()), + remote_addr: response.remote_addr().map(SocketAddr::from), + headers: HeaderMap::from(response.headers().clone()), + response: ArcSwapOption::from_pointee(response), + } + } +} + +#[gen_stub_pymethods] +#[pymethods] +impl WebSocketResponse { + /// Returns whether the response is successful. + /// + /// # Returns + /// + /// A boolean indicating whether the response is successful. + #[getter] + #[inline(always)] + pub fn ok(&self) -> bool { + self.status_code.as_int() == rquest::StatusCode::SWITCHING_PROTOCOLS + } + + /// Returns the status code as integer of the response. + /// + /// # Returns + /// + /// An integer representing the HTTP status code. + #[getter] + #[inline(always)] + pub fn status(&self) -> u16 { + self.status_code.as_int() + } + + /// Returns the HTTP version of the response. + /// + /// # Returns + /// + /// A `Version` object representing the HTTP version of the response. + #[getter] + #[inline(always)] + pub fn version(&self) -> Version { + self.version + } + + /// Returns the headers of the response. + /// + /// # Returns + /// + /// A `HeaderMap` object representing the headers of the response. + #[getter] + #[inline(always)] + pub fn headers(&self) -> HeaderMap { + self.headers.clone() + } + + /// Returns the remote address of the response. + /// + /// # Returns + /// + /// An `IpAddr` object representing the remote address of the response. + #[getter] + #[inline(always)] + pub fn remote_addr(&self) -> Option { + self.remote_addr + } + + /// Returns the TLS peer certificate of the response. + /// + /// # Returns + /// + /// A Python object representing the TLS peer certificate of the response. + pub fn peer_certificate(&self) -> PyResult>> { + let resp_ref = self.response.load(); + let resp = resp_ref.as_ref().ok_or_else(memory_error)?; + if let Some(val) = resp.extensions().get::() { + return Ok(val.peer_certificate().map(ToOwned::to_owned)); + } + + Ok(None) + } + + /// Returns the WebSocket of the response. + pub fn into_websocket<'rt>(&self, py: Python<'rt>) -> PyResult> { + let response = self.into_inner()?; + pyo3_async_runtimes::tokio::future_into_py(py, async move { + response + .into_websocket() + .await + .map(WebSocket::from) + .map_err(wrap_rquest_error) + }) + } + + /// Closes the response connection. + pub fn close(&self) { + let _ = self.into_inner().map(drop); + } +} + +impl WebSocketResponse { + /// Consumes the `WebSocketResponse` and returns the inner `rquest::RespWebSocketResponseonse`. + /// + /// # Returns + /// + /// A `PyResult` containing the inner `rquest::WebSocketResponse` if successful, or an error if the + /// response has already been taken or cannot be unwrapped. + /// + /// # Errors + /// + /// Returns a memory error if the response has already been taken or if the `Arc` cannot be unwrapped. + #[inline(always)] + #[allow(clippy::wrong_self_convention)] + fn into_inner(&self) -> PyResult { + self.response + .swap(None) + .and_then(Arc::into_inner) + .ok_or_else(memory_error) + } +} + +type Sender = Arc>>; +type Receiver = Arc>>; + +#[gen_stub_pyclass] +#[pyclass] +pub struct WebSocket { + protocol: Option, + sender: Sender, + receiver: Receiver, +} + +impl From for WebSocket { + fn from(ws: rquest::WebSocket) -> Self { + let protocol = ws.protocol().map(ToOwned::to_owned); + let (sender, receiver) = ws.split(); + WebSocket { + protocol, + sender: Arc::new(Mutex::new(sender)), + receiver: Arc::new(Mutex::new(receiver)), + } + } +} + +#[pymethods] +impl WebSocket { + pub fn protocol(&self) -> Option<&str> { + self.protocol.as_deref() + } + + pub fn recv<'rt>(&self, py: Python<'rt>) -> PyResult> { + let websocket = self.receiver.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + let mut ws = websocket.lock().await; + if let Ok(Some(val)) = ws.try_next().await { + return Ok(Some(Message(val))); + } + Ok(None) + }) + } + + #[pyo3(signature = (message))] + pub fn send<'rt>(&self, py: Python<'rt>, message: Message) -> PyResult> { + let sender = self.sender.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + let mut ws = sender.lock().await; + ws.send(message.0).await.map_err(wrap_rquest_error) + }) + } + + #[pyo3(signature = (code=None, reason=None))] + pub fn close<'rt>( + &self, + py: Python<'rt>, + code: Option, + reason: Option, + ) -> PyResult> { + let sender = self.sender.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + let mut sender = sender.lock().await; + sender + .send(rquest::Message::Close { + code: rquest::CloseCode::from(code.unwrap_or_default()), + reason, + }) + .await + .map_err(wrap_rquest_error)?; + Ok(()) + }) + } +} + +#[pymethods] +impl WebSocket { + fn __aenter__<'a>(slf: PyRef<'a, Self>, py: Python<'a>) -> PyResult> { + let slf = slf.into_py_any(py)?; + pyo3_async_runtimes::tokio::future_into_py(py, async move { Ok(slf) }) + } + + fn __aexit__<'a>( + &'a mut self, + py: Python<'a>, + _exc_type: &Bound<'a, PyAny>, + _exc_value: &Bound<'a, PyAny>, + _traceback: &Bound<'a, PyAny>, + ) -> PyResult> { + self.close(py, None, None) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct Message(rquest::Message); + +#[pymethods] +impl Message { + fn __str__(&self) -> String { + format!("{:?}", self.0) + } + + fn __repr__(&self) -> String { + self.__str__() + } +} + +#[pymethods] +impl Message { + #[getter] + pub fn data(&self) -> &[u8] { + match &self.0 { + rquest::Message::Text(text) => text.as_bytes(), + rquest::Message::Binary(data) => data, + rquest::Message::Ping(data) => data, + rquest::Message::Pong(data) => data, + _ => &[], + } + } + + pub fn json(&self) -> PyResult { + self.0.json::().map_err(wrap_rquest_error) + } + + #[getter] + pub fn text(&self) -> Option<&str> { + match &self.0 { + rquest::Message::Text(text) => Some(text), + _ => None, + } + } + + #[getter] + pub fn binary(&self) -> Option<&[u8]> { + match &self.0 { + rquest::Message::Binary(data) => Some(data), + _ => None, + } + } + + #[getter] + pub fn ping(&self) -> Option<&[u8]> { + match &self.0 { + rquest::Message::Ping(data) => Some(data), + _ => None, + } + } + + #[getter] + pub fn pong(&self) -> Option<&[u8]> { + match &self.0 { + rquest::Message::Pong(data) => Some(data), + _ => None, + } + } + + #[getter] + pub fn close(&self) -> Option<(u16, Option<&str>)> { + match &self.0 { + rquest::Message::Close { code, reason } => Some(( + match *code { + rquest::CloseCode::Normal => 1000, + rquest::CloseCode::Away => 1001, + rquest::CloseCode::Protocol => 1002, + rquest::CloseCode::Unsupported => 1003, + rquest::CloseCode::Status => 1005, + rquest::CloseCode::Abnormal => 1006, + rquest::CloseCode::Invalid => 1007, + rquest::CloseCode::Policy => 1008, + rquest::CloseCode::Size => 1009, + rquest::CloseCode::Extension => 1010, + rquest::CloseCode::Error => 1011, + rquest::CloseCode::Restart => 1012, + rquest::CloseCode::Again => 1013, + rquest::CloseCode::Tls => 1015, + rquest::CloseCode::Reserved(v) + | rquest::CloseCode::Iana(v) + | rquest::CloseCode::Library(v) + | rquest::CloseCode::Bad(v) => v, + _ => return None, + }, + reason.as_deref(), + )), + _ => None, + } + } + + #[staticmethod] + #[pyo3(signature = (text))] + #[inline] + pub fn from_text(text: &str) -> Self { + Message(rquest::Message::Text(text.to_owned())) + } + + #[staticmethod] + #[pyo3(signature = (data))] + #[inline] + pub fn from_binary(data: Vec) -> Self { + Message(rquest::Message::Binary(data)) + } + + #[staticmethod] + #[pyo3(signature = (data))] + #[inline] + pub fn from_ping(data: Vec) -> Self { + Message(rquest::Message::Ping(data)) + } + + #[staticmethod] + #[pyo3(signature = (data))] + #[inline] + pub fn from_pong(data: Vec) -> Self { + Message(rquest::Message::Pong(data)) + } + + #[staticmethod] + #[pyo3(signature = (code, reason=None))] + #[inline] + pub fn from_close(code: u16, reason: Option) -> Self { + Message(rquest::Message::Close { + code: rquest::CloseCode::from(code), + reason, + }) + } +} From b0371e92eb676d0d62990b24972e199ca0f705e9 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Mon, 17 Feb 2025 19:36:41 +0800 Subject: [PATCH 2/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 107f6f19..f00aa2f0 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Asynchronous Python HTTP Client with Black Magic, powered by FFI from [rquest](h - Redirect Policy - Cookie Store - HTTP Proxies +- Websocket Upgrade - HTTPS via BoringSSL - Perfectly Chrome, Safari, and Firefox From 163985dc5fc3272f5b356ed25de8d76405a05ed7 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Mon, 17 Feb 2025 21:07:10 +0800 Subject: [PATCH 3/5] update docs --- README.md | 2 +- rnet.pyi | 516 ++++++++++++++++++++++----------------------- src/response/ws.rs | 157 +++++++++++--- 3 files changed, 382 insertions(+), 293 deletions(-) diff --git a/README.md b/README.md index f00aa2f0..1b3494af 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Asynchronous Python HTTP Client with Black Magic, powered by FFI from [rquest](h - Redirect Policy - Cookie Store - HTTP Proxies -- Websocket Upgrade +- WebSocket Upgrade - HTTPS via BoringSSL - Perfectly Chrome, Safari, and Firefox diff --git a/rnet.pyi b/rnet.pyi index fe8f9d68..fd7d8f84 100644 --- a/rnet.pyi +++ b/rnet.pyi @@ -8,287 +8,287 @@ class Client: r""" A client for making HTTP requests. """ - - def __new__(cls, **kwds): ... - def get(self, url: builtins.str, **kwds) -> typing.Any: + def __new__(cls,**kwds): ... + def get(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends a GET request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.get("https://httpbin.org/get") print(await response.text()) - + asyncio.run(main()) ``` """ ... - def post(self, url: builtins.str, **kwds) -> typing.Any: + def post(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends a POST request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.post("://httpbin.org/post", json={"key": "value"}) print(await response.text()) - + asyncio.run(main()) ``` """ ... - def put(self, url: builtins.str, **kwds) -> typing.Any: + def put(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends a PUT request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.put("https://httpbin.org/put", json={"key": "value"}) print(await response.text()) - + asyncio.run(main()) ``` """ ... - def patch(self, url: builtins.str, **kwds) -> typing.Any: + def patch(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends a PATCH request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.patch("https://httpbin.org/patch", json={"key": "value"}) print(await response.text()) - + asyncio.run(main()) ``` """ ... - def delete(self, url: builtins.str, **kwds) -> typing.Any: + def delete(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends a DELETE request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.delete("https://httpbin.org/delete") print(await response.text()) - + asyncio.run(main()) ``` """ ... - def head(self, url: builtins.str, **kwds) -> typing.Any: + def head(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends a HEAD request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.head("https://httpbin.org/head") print(response.status_code) - + asyncio.run(main()) ``` """ ... - def options(self, url: builtins.str, **kwds) -> typing.Any: + def options(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends an OPTIONS request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.options("https://httpbin.org/options") print(response.headers) - + asyncio.run(main()) ``` """ ... - def trace(self, url: builtins.str, **kwds) -> typing.Any: + def trace(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends a TRACE request. - + # Arguments - + * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio - + async def main(): client = rnet.Client() response = await client.trace("https://httpbin.org/trace") print(response.headers) - + asyncio.run(main()) ``` """ ... - def request(self, method: Method, url: builtins.str, **kwds) -> typing.Any: + def request(self, method:Method, url:builtins.str, **kwds) -> typing.Any: r""" Sends a request with the given method and URL. - + # Arguments - + * `method` - The HTTP method to use. * `url` - The URL to send the request to. * `**kwds` - Additional request parameters. - + # Returns - + A `Response` object. - + # Examples - + ```python import rnet import asyncio from rnet import Method - + async def main(): client = rnet.Client() response = await client.request(Method.GET, "https://httpbin.org/get") print(await response.text()) - + asyncio.run(main()) ``` """ ... - def websocket(self, url: builtins.str, **kwds) -> typing.Any: + def websocket(self, url:builtins.str, **kwds) -> typing.Any: r""" Sends a WebSocket request. """ ... + class ClientParams: r""" The parameters for a request. - + # Examples - + ```python import rnet from rnet import Impersonate, Version - + params = rnet.RequestParams( impersonate=Impersonate.Chrome100, default_headers={"Content-Type": "application/json"}, @@ -302,13 +302,12 @@ class ClientParams: http2_only=True, referer=True ) - + response = await rnet.get("https://www.rust-lang.org", **params) body = await response.text() print(body) ``` """ - impersonate: typing.Optional[Impersonate] impersonate_os: typing.Optional[ImpersonateOS] impersonate_skip_http2: typing.Optional[builtins.bool] @@ -342,43 +341,42 @@ class ClientParams: class HeaderMap: r""" A HTTP header map. - + # Examples - + ```python import rnet from rnet import Method - + async def main(): resp = await rnet.request(Method.GET, "https://www.google.com/") print("Headers: ", resp.headers.to_dict()) - + if __name__ == "__main__": import asyncio asyncio.run(main()) ``` """ - def to_dict(self) -> dict: r""" Converts the header map to a Python dictionary. - + # Returns - + A Python dictionary representing the headers. """ ... - def __getitem__(self, key: builtins.str) -> typing.Optional[typing.Any]: + def __getitem__(self, key:builtins.str) -> typing.Optional[typing.Any]: r""" Gets the value of the specified header. - + # Arguments - + * `key` - The name of the header. - + # Returns - + An optional byte slice representing the value of the header. """ ... @@ -389,11 +387,11 @@ class HeaderMap: """ ... + class Impersonate: r""" A impersonate. """ - def __str__(self) -> builtins.str: r""" Returns a string representation of the impersonate. @@ -406,6 +404,7 @@ class Impersonate: """ ... + class ImpersonateOS: def __str__(self) -> builtins.str: r""" @@ -419,97 +418,103 @@ class ImpersonateOS: """ ... + +class Message: + r""" + A WebSocket message. + """ + ... + class Method: r""" A HTTP method. """ - ... class Proxy: r""" A proxy server for a request. """ - @staticmethod - def http(url: builtins.str) -> Proxy: + def http(url:builtins.str) -> Proxy: r""" Creates a new HTTP proxy. - + # Arguments - + * `url` - The URL of the proxy server. - + # Returns - + A new `Proxy` instance. - + # Examples - + ```python import rnet - + proxy = rnet.Proxy.http("http://proxy.example.com") ``` """ ... @staticmethod - def https(url: builtins.str) -> Proxy: + def https(url:builtins.str) -> Proxy: r""" Creates a new HTTPS proxy. - + # Arguments - + * `url` - The URL of the proxy server. - + # Returns - + A new `Proxy` instance. - + # Examples - + ```python import rnet - + proxy = rnet.Proxy.https("https://proxy.example.com") ``` """ ... @staticmethod - def all(url: builtins.str) -> Proxy: + def all(url:builtins.str) -> Proxy: r""" Creates a new HTTPS proxy. - + # Arguments - + * `url` - The URL of the proxy server. - + # Returns - + A new `Proxy` instance. - + # Examples - + ```python import rnet - + proxy = rnet.Proxy.all("https://proxy.example.com") ``` """ ... + class RequestParams: r""" The parameters for a request. - + # Examples - + ```python import rnet from rnet import Impersonate, Version - + params = rnet.RequestParams( impersonate=Impersonate.Chrome100, version=Version.HTTP_2, @@ -524,13 +529,12 @@ class RequestParams: http2_only=True, referer=True ) - + response = await rnet.get("https://www.rust-lang.org", **params) body = await response.text() print(body) ``` """ - proxy: typing.Optional[builtins.str] interface: typing.Optional[builtins.str] timeout: typing.Optional[builtins.int] @@ -546,13 +550,13 @@ class RequestParams: class Response: r""" A response from a request. - + # Examples - + ```python import asyncio import rnet - + async def main(): response = await rnet.get("https://www.rust-lang.org") print("Status Code: ", response.status_code) @@ -562,15 +566,14 @@ class Response: print("Content-Length: ", response.content_length) print("Encoding: ", response.encoding) print("Remote Address: ", response.remote_addr) - + text_content = await response.text() print("Text: ", text_content) - + if __name__ == "__main__": asyncio.run(main()) ``` """ - url: builtins.str ok: builtins.bool status: builtins.int @@ -583,9 +586,9 @@ class Response: def peer_certificate(self) -> typing.Optional[builtins.list[builtins.int]]: r""" Returns the TLS peer certificate of the response. - + # Returns - + A Python object representing the TLS peer certificate of the response. """ ... @@ -593,23 +596,23 @@ class Response: def text(self) -> typing.Any: r""" Returns the text content of the response. - + # Returns - + A Python object representing the text content of the response. """ ... - def text_with_charset(self, encoding: builtins.str) -> typing.Any: + def text_with_charset(self, encoding:builtins.str) -> typing.Any: r""" Returns the text content of the response with a specific charset. - + # Arguments - + * `default_encoding` - The default encoding to use if the charset is not specified. - + # Returns - + A Python object representing the text content of the response. """ ... @@ -617,9 +620,9 @@ class Response: def json(self) -> typing.Any: r""" Returns the JSON content of the response. - + # Returns - + A Python object representing the JSON content of the response. """ ... @@ -627,9 +630,9 @@ class Response: def json_str(self) -> typing.Any: r""" Returns the JSON string content of the response. - + # Returns - + A Python object representing the JSON content of the response. """ ... @@ -637,9 +640,9 @@ class Response: def json_str_pretty(self) -> typing.Any: r""" Returns the JSON pretty string content of the response. - + # Returns - + A Python object representing the JSON content of the response. """ ... @@ -647,9 +650,9 @@ class Response: def bytes(self) -> typing.Any: r""" Returns the bytes content of the response. - + # Returns - + A Python object representing the bytes content of the response. """ ... @@ -657,9 +660,9 @@ class Response: def stream(self) -> Streamer: r""" Returns the stream content of the response. - + # Returns - + A Python object representing the stream content of the response. """ ... @@ -670,11 +673,11 @@ class Response: """ ... + class SocketAddr: r""" A IP socket address. """ - def ip(self) -> typing.Any: r""" Returns the IP address of the socket address. @@ -693,11 +696,11 @@ class SocketAddr: """ ... + class StatusCode: r""" HTTP status code. """ - def as_int(self) -> builtins.int: r""" Return the status code as an integer. @@ -740,7 +743,9 @@ class StatusCode: """ ... - def __repr__(self) -> builtins.str: ... + def __repr__(self) -> builtins.str: + ... + class Streamer: r""" @@ -749,14 +754,14 @@ class Streamer: This is used to stream the response content. This is used in the `stream` method of the `Response` class. This is used in an asynchronous for loop in Python. - + # Examples - + ```python import asyncio import rnet from rnet import Method, Impersonate - + async def main(): resp = await rnet.get("https://httpbin.org/stream/20") print("Status Code: ", resp.status_code) @@ -766,25 +771,24 @@ class Streamer: print("Content-Length: ", resp.content_length) print("Encoding: ", resp.encoding) print("Remote Address: ", resp.remote_addr) - + streamer = resp.stream() async for chunk in streamer: print("Chunk: ", chunk) await asyncio.sleep(0.1) - + if __name__ == "__main__": asyncio.run(main()) ``` """ - def __aiter__(self) -> Streamer: r""" Returns the `Streamer` instance itself to be used as an asynchronous iterator. - + This method allows the `Streamer` to be used in an asynchronous for loop in Python. - + # Returns - + The `Streamer` instance itself. """ ... @@ -792,13 +796,13 @@ class Streamer: def __anext__(self) -> typing.Optional[typing.Any]: r""" Returns the next chunk of the response as an asynchronous iterator. - + This method implements the `__anext__` method for the `Streamer` class, allowing it to be used as an asynchronous iterator in Python. It returns the next chunk of the response or raises `PyStopAsyncIteration` if the iterator is exhausted. - + # Returns - + A `PyResult` containing an `Option`. If there is a next chunk, it returns `Some(PyObject)`. If the iterator is exhausted, it raises `PyStopAsyncIteration`. """ @@ -812,11 +816,11 @@ class Streamer: """ ... + class Version: r""" A HTTP version. """ - def __str__(self) -> builtins.str: r""" Returns a string representation of the version. @@ -829,18 +833,23 @@ class Version: """ ... -class WebSocket: ... + +class WebSocket: + r""" + A WebSocket connection. + """ + ... class WebSocketParams: r""" The parameters for a WebSocket request. - + # Examples - + ```python import rnet from rnet import Impersonate, Version - + params = rnet.WebSocketParams( proxy="http://proxy.example.com", local_address="192.168.1.1", @@ -851,16 +860,15 @@ class WebSocketParams: basic_auth=("user", "password"), query=[("key1", "value1"), ("key2", "value2")] ) - + async with rnet.websocket("wss://echo.websocket.org") as ws: await ws.send("Hello, World!") message = await ws.recv() print(message) - + asyncio.run(run()) ``` """ - proxy: typing.Optional[builtins.str] interface: typing.Optional[builtins.str] auth: typing.Optional[builtins.str] @@ -870,32 +878,8 @@ class WebSocketParams: class WebSocketResponse: r""" - A response from a request. - - # Examples - - ```python - import asyncio - import rnet - - async def main(): - response = await rnet.get("https://www.rust-lang.org") - print("Status Code: ", response.status_code) - print("Version: ", response.version) - print("Response URL: ", response.url) - print("Headers: ", response.headers.to_dict()) - print("Content-Length: ", response.content_length) - print("Encoding: ", response.encoding) - print("Remote Address: ", response.remote_addr) - - text_content = await response.text() - print("Text: ", text_content) - - if __name__ == "__main__": - asyncio.run(main()) - ``` + A WebSocket response. """ - ok: builtins.bool status: builtins.int version: Version @@ -904,9 +888,9 @@ class WebSocketResponse: def peer_certificate(self) -> typing.Optional[builtins.list[builtins.int]]: r""" Returns the TLS peer certificate of the response. - + # Returns - + A Python object representing the TLS peer certificate of the response. """ ... @@ -923,55 +907,56 @@ class WebSocketResponse: """ ... -def delete(url: builtins.str, **kwds) -> typing.Any: + +def delete(url:builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `DELETE` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.delete("https://httpbin.org/anything") body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def get(url: builtins.str, **kwds) -> typing.Any: +def get(url:builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `GET` request. - + See also the methods on the [`rquest::Response`](./struct.Response.html) type. - + **NOTE**: This function creates a new internal `Client` on each call, and so should not be used if making many requests. Create a [`Client`](./struct.Client.html) instead. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.get("https://httpbin.org/anything") body = await response.text() print(body) - + asyncio.run(run()) ``` - + # Errors - + This function fails if: - + - native TLS backend cannot be initialized - supplied `Url` cannot be parsed - there was an error while sending request @@ -979,165 +964,166 @@ def get(url: builtins.str, **kwds) -> typing.Any: """ ... -def head(url: builtins.str, **kwds) -> typing.Any: +def head(url:builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `HEAD` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.head("https://httpbin.org/anything") print(response.headers) - + asyncio.run(run()) ``` """ ... -def options(url: builtins.str, **kwds) -> typing.Any: +def options(url:builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make an `OPTIONS` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.options("https://httpbin.org/anything") print(response.headers) - + asyncio.run(run()) ``` """ ... -def patch(url: builtins.str, **kwds) -> typing.Any: +def patch(url:builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `PATCH` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.patch("https://httpbin.org/anything", json={"key": "value"}) body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def post(url: builtins.str, **kwds) -> typing.Any: +def post(url:builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `POST` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.post("https://httpbin.org/anything", json={"key": "value"}) body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def put(url: builtins.str, **kwds) -> typing.Any: +def put(url:builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `PUT` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.put("https://httpbin.org/anything", json={"key": "value"}) body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def request(method: Method, url: builtins.str, **kwds) -> typing.Any: +def request(method:Method, url:builtins.str, **kwds) -> typing.Any: r""" Make a request with the given parameters. - + This function allows you to make a request with the specified parameters encapsulated in a `Request` object. - + # Examples - + ```python import rnet import asyncio from rnet import Method - + async def run(): response = await rnet.request(Method.GET, "https://www.rust-lang.org") body = await response.text() print(body) - + asyncio.run(run()) ``` """ ... -def trace(url: builtins.str, **kwds) -> typing.Any: +def trace(url:builtins.str, **kwds) -> typing.Any: r""" Shortcut method to quickly make a `TRACE` request. - + # Examples - + ```python import rnet import asyncio - + async def run(): response = await rnet.trace("https://www.rust-lang.org") print(response.headers) - + asyncio.run(run()) ``` """ ... -def websocket(url: builtins.str, **kwds) -> typing.Any: +def websocket(url:builtins.str, **kwds) -> typing.Any: r""" Make a WebSocket connection with the given parameters. - + This function allows you to make a WebSocket connection with the specified parameters encapsulated in a `WebSocket` object. - + # Examples - + ```python import rnet import asyncio - + async def run(): async with rnet.websocket("wss://echo.websocket.org") as ws: await ws.send("Hello, World!") message = await ws.recv() print(message) - + asyncio.run(run()) ``` """ ... + diff --git a/src/response/ws.rs b/src/response/ws.rs index 39e35bd1..7fe4b229 100644 --- a/src/response/ws.rs +++ b/src/response/ws.rs @@ -14,30 +14,7 @@ use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use rquest::TlsInfo; use tokio::sync::Mutex; -/// A response from a request. -/// -/// # Examples -/// -/// ```python -/// import asyncio -/// import rnet -/// -/// async def main(): -/// response = await rnet.get("https://www.rust-lang.org") -/// print("Status Code: ", response.status_code) -/// print("Version: ", response.version) -/// print("Response URL: ", response.url) -/// print("Headers: ", response.headers.to_dict()) -/// print("Content-Length: ", response.content_length) -/// print("Encoding: ", response.encoding) -/// print("Remote Address: ", response.remote_addr) -/// -/// text_content = await response.text() -/// print("Text: ", text_content) -/// -/// if __name__ == "__main__": -/// asyncio.run(main()) -/// ``` +/// A WebSocket response. #[gen_stub_pyclass] #[pyclass] pub struct WebSocketResponse { @@ -175,6 +152,7 @@ impl WebSocketResponse { type Sender = Arc>>; type Receiver = Arc>>; +/// A WebSocket connection. #[gen_stub_pyclass] #[pyclass] pub struct WebSocket { @@ -197,10 +175,24 @@ impl From for WebSocket { #[pymethods] impl WebSocket { + /// Returns the WebSocket protocol. + /// + /// # Returns + /// + /// An optional string representing the WebSocket protocol. pub fn protocol(&self) -> Option<&str> { self.protocol.as_deref() } + /// Receives a message from the WebSocket. + /// + /// # Arguments + /// + /// * `py` - The Python runtime. + /// + /// # Returns + /// + /// A `PyResult` containing a `Bound` object with the received message, or `None` if no message is received. pub fn recv<'rt>(&self, py: Python<'rt>) -> PyResult> { let websocket = self.receiver.clone(); pyo3_async_runtimes::tokio::future_into_py(py, async move { @@ -212,6 +204,16 @@ impl WebSocket { }) } + /// Sends a message to the WebSocket. + /// + /// # Arguments + /// + /// * `py` - The Python runtime. + /// * `message` - The message to send. + /// + /// # Returns + /// + /// A `PyResult` containing a `Bound` object. #[pyo3(signature = (message))] pub fn send<'rt>(&self, py: Python<'rt>, message: Message) -> PyResult> { let sender = self.sender.clone(); @@ -221,6 +223,17 @@ impl WebSocket { }) } + /// Closes the WebSocket connection. + /// + /// # Arguments + /// + /// * `py` - The Python runtime. + /// * `code` - An optional close code. + /// * `reason` - An optional reason for closing. + /// + /// # Returns + /// + /// A `PyResult` containing a `Bound` object. #[pyo3(signature = (code=None, reason=None))] pub fn close<'rt>( &self, @@ -261,23 +274,37 @@ impl WebSocket { } } +/// A WebSocket message. +#[gen_stub_pyclass] #[pyclass] #[derive(Clone)] pub struct Message(rquest::Message); #[pymethods] impl Message { + /// Returns a string representation of the message. + /// + /// # Returns + /// + /// A string representing the message. fn __str__(&self) -> String { format!("{:?}", self.0) } + /// Returns a string representation of the message. + /// + /// # Returns + /// + /// A string representing the message. fn __repr__(&self) -> String { self.__str__() } -} -#[pymethods] -impl Message { + /// Returns the data of the message as bytes. + /// + /// # Returns + /// + /// A byte slice representing the data of the message. #[getter] pub fn data(&self) -> &[u8] { match &self.0 { @@ -289,10 +316,20 @@ impl Message { } } + /// Returns the JSON representation of the message. + /// + /// # Returns + /// + /// A `PyResult` containing the JSON representation of the message. pub fn json(&self) -> PyResult { self.0.json::().map_err(wrap_rquest_error) } + /// Returns the text of the message if it is a text message. + /// + /// # Returns + /// + /// An optional string representing the text of the message. #[getter] pub fn text(&self) -> Option<&str> { match &self.0 { @@ -301,6 +338,11 @@ impl Message { } } + /// 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. #[getter] pub fn binary(&self) -> Option<&[u8]> { match &self.0 { @@ -309,6 +351,11 @@ impl Message { } } + /// 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. #[getter] pub fn ping(&self) -> Option<&[u8]> { match &self.0 { @@ -317,6 +364,11 @@ impl Message { } } + /// 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. #[getter] pub fn pong(&self) -> Option<&[u8]> { match &self.0 { @@ -325,6 +377,11 @@ impl Message { } } + /// 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. #[getter] pub fn close(&self) -> Option<(u16, Option<&str>)> { match &self.0 { @@ -356,6 +413,15 @@ impl Message { } } + /// Creates a new text message. + /// + /// # Arguments + /// + /// * `text` - The text content of the message. + /// + /// # Returns + /// + /// A new `Message` instance containing the text message. #[staticmethod] #[pyo3(signature = (text))] #[inline] @@ -363,6 +429,15 @@ impl Message { Message(rquest::Message::Text(text.to_owned())) } + /// Creates a new binary message. + /// + /// # Arguments + /// + /// * `data` - The binary data of the message. + /// + /// # Returns + /// + /// A new `Message` instance containing the binary message. #[staticmethod] #[pyo3(signature = (data))] #[inline] @@ -370,6 +445,15 @@ impl Message { Message(rquest::Message::Binary(data)) } + /// Creates a new ping message. + /// + /// # Arguments + /// + /// * `data` - The ping data of the message. + /// + /// # Returns + /// + /// A new `Message` instance containing the ping message. #[staticmethod] #[pyo3(signature = (data))] #[inline] @@ -377,6 +461,15 @@ impl Message { Message(rquest::Message::Ping(data)) } + /// Creates a new pong message. + /// + /// # Arguments + /// + /// * `data` - The pong data of the message. + /// + /// # Returns + /// + /// A new `Message` instance containing the pong message. #[staticmethod] #[pyo3(signature = (data))] #[inline] @@ -384,6 +477,16 @@ impl Message { Message(rquest::Message::Pong(data)) } + /// Creates a new close message. + /// + /// # Arguments + /// + /// * `code` - The close code. + /// * `reason` - An optional reason for closing. + /// + /// # Returns + /// + /// A new `Message` instance containing the close message. #[staticmethod] #[pyo3(signature = (code, reason=None))] #[inline] From 981796d4854d9465ec231c2773bcdef08e54ee5d Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Mon, 17 Feb 2025 21:12:59 +0800 Subject: [PATCH 4/5] update docs --- examples/ws.py | 4 +-- src/response/ws.rs | 74 ++++++++++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/examples/ws.py b/examples/ws.py index cc8e2b6e..af7669a7 100644 --- a/examples/ws.py +++ b/examples/ws.py @@ -9,7 +9,7 @@ async def send_message(ws): print(f"Sending: Message {i + 1}") await ws.send(Message.from_text(f"Message {i + 1}")) await asyncio.sleep(1) - + async def receive_message(ws): while True: @@ -48,4 +48,4 @@ async def close_ws(): if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/src/response/ws.rs b/src/response/ws.rs index 7fe4b229..83b65e6a 100644 --- a/src/response/ws.rs +++ b/src/response/ws.rs @@ -282,22 +282,13 @@ pub struct Message(rquest::Message); #[pymethods] impl Message { - /// Returns a string representation of the message. - /// - /// # Returns - /// - /// A string representing the message. - fn __str__(&self) -> String { - format!("{:?}", self.0) - } - - /// Returns a string representation of the message. + /// Returns the JSON representation of the message. /// /// # Returns /// - /// A string representing the message. - fn __repr__(&self) -> String { - self.__str__() + /// A `PyResult` containing the JSON representation of the message. + pub fn json(&self) -> PyResult { + self.0.json::().map_err(wrap_rquest_error) } /// Returns the data of the message as bytes. @@ -316,28 +307,6 @@ impl Message { } } - /// Returns the JSON representation of the message. - /// - /// # Returns - /// - /// A `PyResult` containing the JSON representation of the message. - pub fn json(&self) -> PyResult { - self.0.json::().map_err(wrap_rquest_error) - } - - /// Returns the text of the message if it is a text message. - /// - /// # Returns - /// - /// An optional string representing the text of the message. - #[getter] - pub fn text(&self) -> Option<&str> { - match &self.0 { - rquest::Message::Text(text) => Some(text), - _ => None, - } - } - /// Returns the binary data of the message if it is a binary message. /// /// # Returns @@ -376,6 +345,41 @@ impl Message { _ => None, } } +} + +#[gen_stub_pymethods] +#[pymethods] +impl Message { + /// Returns a string representation of the message. + /// + /// # Returns + /// + /// A string representing the message. + fn __str__(&self) -> String { + format!("{:?}", self.0) + } + + /// Returns a string representation of the message. + /// + /// # Returns + /// + /// A string representing the message. + fn __repr__(&self) -> String { + self.__str__() + } + + /// Returns the text of the message if it is a text message. + /// + /// # Returns + /// + /// An optional string representing the text of the message. + #[getter] + pub fn text(&self) -> Option<&str> { + match &self.0 { + rquest::Message::Text(text) => Some(text), + _ => None, + } + } /// Returns the close code and reason of the message if it is a close message. /// From 29c89d4fa360ff423411e7ee06c198227273072e Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Mon, 17 Feb 2025 21:13:39 +0800 Subject: [PATCH 5/5] update docs --- rnet.pyi | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/rnet.pyi b/rnet.pyi index fd7d8f84..96aec836 100644 --- a/rnet.pyi +++ b/rnet.pyi @@ -423,7 +423,104 @@ class Message: r""" A WebSocket message. """ - ... + text: typing.Optional[builtins.str] + close: typing.Optional[tuple[builtins.int, typing.Optional[builtins.str]]] + def __str__(self) -> builtins.str: + r""" + Returns a string representation of the message. + + # Returns + + A string representing the message. + """ + ... + + def __repr__(self) -> builtins.str: + r""" + Returns a string representation of the message. + + # Returns + + A string representing the message. + """ + ... + + @staticmethod + def from_text(text:builtins.str) -> Message: + r""" + Creates a new text message. + + # Arguments + + * `text` - The text content of the message. + + # Returns + + A new `Message` instance containing the text message. + """ + ... + + @staticmethod + def from_binary(data:typing.Sequence[builtins.int]) -> Message: + r""" + Creates a new binary message. + + # Arguments + + * `data` - The binary data of the message. + + # Returns + + A new `Message` instance containing the binary message. + """ + ... + + @staticmethod + def from_ping(data:typing.Sequence[builtins.int]) -> Message: + r""" + Creates a new ping message. + + # Arguments + + * `data` - The ping data of the message. + + # Returns + + A new `Message` instance containing the ping message. + """ + ... + + @staticmethod + def from_pong(data:typing.Sequence[builtins.int]) -> Message: + r""" + Creates a new pong message. + + # Arguments + + * `data` - The pong data of the message. + + # Returns + + A new `Message` instance containing the pong message. + """ + ... + + @staticmethod + def from_close(code:builtins.int, reason:typing.Optional[builtins.str]=None) -> Message: + r""" + Creates a new close message. + + # Arguments + + * `code` - The close code. + * `reason` - An optional reason for closing. + + # Returns + + A new `Message` instance containing the close message. + """ + ... + class Method: r"""