Skip to main content

nautilus_network/python/
mod.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings from [PyO3](https://pyo3.rs).
17
18// We need to allow `unexpected_cfgs` because the PyO3 macros internally check for
19// the `gil-refs` feature. We don’t define or enable `gil-refs` ourselves (due to a
20// memory leak), so the compiler raises an error about an unknown cfg feature.
21// This attribute prevents those errors without actually enabling `gil-refs`.
22#![allow(unexpected_cfgs)]
23
24pub mod http;
25pub mod socket;
26pub mod websocket;
27
28use std::num::NonZeroU32;
29
30use nautilus_core::python::to_pyexception;
31use pyo3::prelude::*;
32
33use crate::{
34    python::{
35        http::{HttpClientBuildError, HttpError, HttpInvalidProxyError, HttpTimeoutError},
36        websocket::WebSocketClientError,
37    },
38    ratelimiter::quota::Quota,
39};
40
41#[pymethods]
42impl Quota {
43    /// Construct a quota for a number of requests per second.
44    ///
45    /// # Errors
46    ///
47    /// Returns a `PyErr` if the max burst capacity is 0
48    #[staticmethod]
49    pub fn rate_per_second(max_burst: u32) -> PyResult<Self> {
50        match NonZeroU32::new(max_burst) {
51            Some(max_burst) => Ok(Self::per_second(max_burst)),
52            None => Err(to_pyexception(
53                "Max burst capacity should be a non-zero integer",
54            )),
55        }
56    }
57
58    /// Construct a quota for a number of requests per minute.
59    ///
60    /// # Errors
61    ///
62    /// Returns a `PyErr` if the max burst capacity is 0
63    #[staticmethod]
64    pub fn rate_per_minute(max_burst: u32) -> PyResult<Self> {
65        match NonZeroU32::new(max_burst) {
66            Some(max_burst) => Ok(Self::per_minute(max_burst)),
67            None => Err(to_pyexception(
68                "Max burst capacity should be a non-zero integer",
69            )),
70        }
71    }
72
73    /// Construct a quota for a number of requests per hour.
74    ///
75    /// # Errors
76    ///
77    /// Returns a `PyErr` if the max burst capacity is 0
78    #[staticmethod]
79    pub fn rate_per_hour(max_burst: u32) -> PyResult<Self> {
80        match NonZeroU32::new(max_burst) {
81            Some(max_burst) => Ok(Self::per_hour(max_burst)),
82            None => Err(to_pyexception(
83                "Max burst capacity should be a non-zero integer",
84            )),
85        }
86    }
87}
88
89/// Loaded as `nautilus_pyo3.network`.
90///
91/// # Errors
92///
93/// Returns a `PyErr` if registering any module components fails.
94#[pymodule]
95pub fn network(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
96    m.add_class::<crate::http::HttpClient>()?;
97    m.add_class::<crate::http::HttpMethod>()?;
98    m.add_class::<crate::http::HttpResponse>()?;
99    m.add_class::<crate::ratelimiter::quota::Quota>()?;
100    m.add_class::<crate::websocket::WebSocketClient>()?;
101    m.add_class::<crate::websocket::WebSocketConfig>()?;
102    m.add_class::<crate::socket::SocketClient>()?;
103    m.add_class::<crate::socket::SocketConfig>()?;
104
105    m.add(
106        "WebSocketClientError",
107        m.py().get_type::<WebSocketClientError>(),
108    )?;
109    m.add("HttpError", m.py().get_type::<HttpError>())?;
110    m.add("HttpTimeoutError", m.py().get_type::<HttpTimeoutError>())?;
111    m.add(
112        "HttpInvalidProxyError",
113        m.py().get_type::<HttpInvalidProxyError>(),
114    )?;
115    m.add(
116        "HttpClientBuildError",
117        m.py().get_type::<HttpClientBuildError>(),
118    )?;
119
120    m.add_function(wrap_pyfunction!(http::http_get, m)?)?;
121    m.add_function(wrap_pyfunction!(http::http_post, m)?)?;
122    m.add_function(wrap_pyfunction!(http::http_patch, m)?)?;
123    m.add_function(wrap_pyfunction!(http::http_delete, m)?)?;
124    m.add_function(wrap_pyfunction!(http::http_download, m)?)?;
125
126    Ok(())
127}