nautilus_network/python/
mod.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.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 pyo3::{PyTypeCheck, exceptions::PyException, prelude::*};
31
32use crate::{
33    python::{
34        http::{HttpError, HttpTimeoutError},
35        websocket::WebSocketClientError,
36    },
37    ratelimiter::quota::Quota,
38};
39
40#[pymethods]
41impl Quota {
42    /// Construct a quota for a number of requests per second.
43    ///
44    /// # Errors
45    ///
46    /// Returns a `PyErr` if the max burst capacity is 0
47    #[staticmethod]
48    pub fn rate_per_second(max_burst: u32) -> PyResult<Self> {
49        match NonZeroU32::new(max_burst) {
50            Some(max_burst) => Ok(Self::per_second(max_burst)),
51            None => Err(PyErr::new::<PyException, _>(
52                "Max burst capacity should be a non-zero integer",
53            )),
54        }
55    }
56
57    /// Construct a quota for a number of requests per minute.
58    ///
59    /// # Errors
60    ///
61    /// Returns a `PyErr` if the max burst capacity is 0
62    #[staticmethod]
63    pub fn rate_per_minute(max_burst: u32) -> PyResult<Self> {
64        match NonZeroU32::new(max_burst) {
65            Some(max_burst) => Ok(Self::per_minute(max_burst)),
66            None => Err(PyErr::new::<PyException, _>(
67                "Max burst capacity should be a non-zero integer",
68            )),
69        }
70    }
71
72    /// Construct a quota for a number of requests per hour.
73    ///
74    /// # Errors
75    ///
76    /// Returns a `PyErr` if the max burst capacity is 0
77    #[staticmethod]
78    pub fn rate_per_hour(max_burst: u32) -> PyResult<Self> {
79        match NonZeroU32::new(max_burst) {
80            Some(max_burst) => Ok(Self::per_hour(max_burst)),
81            None => Err(PyErr::new::<PyException, _>(
82                "Max burst capacity should be a non-zero integer",
83            )),
84        }
85    }
86}
87
88/// Loaded as `nautilus_pyo3.network`.
89///
90/// # Errors
91///
92/// Returns a `PyErr` if registering any module components fails.
93#[pymodule]
94pub fn network(_: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
95    m.add_class::<crate::http::HttpClient>()?;
96    m.add_class::<crate::http::HttpMethod>()?;
97    m.add_class::<crate::http::HttpResponse>()?;
98    m.add_class::<crate::ratelimiter::quota::Quota>()?;
99    m.add_class::<crate::websocket::WebSocketClient>()?;
100    m.add_class::<crate::websocket::WebSocketConfig>()?;
101    m.add_class::<crate::socket::SocketClient>()?;
102    m.add_class::<crate::socket::SocketConfig>()?;
103
104    // Add error classes
105    m.add(
106        <WebSocketClientError as PyTypeCheck>::NAME,
107        m.py().get_type::<WebSocketClientError>(),
108    )?;
109    m.add(
110        <HttpError as PyTypeCheck>::NAME,
111        m.py().get_type::<HttpError>(),
112    )?;
113    m.add(
114        <HttpTimeoutError as PyTypeCheck>::NAME,
115        m.py().get_type::<HttpTimeoutError>(),
116    )?;
117
118    Ok(())
119}