pyo3/conversions/std/
ipaddr.rs

1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
2
3use crate::conversion::IntoPyObject;
4use crate::exceptions::PyValueError;
5use crate::instance::Bound;
6use crate::sync::GILOnceCell;
7use crate::types::any::PyAnyMethods;
8use crate::types::string::PyStringMethods;
9use crate::types::PyType;
10use crate::{intern, FromPyObject, Py, PyAny, PyErr, PyObject, PyResult, Python};
11#[allow(deprecated)]
12use crate::{IntoPy, ToPyObject};
13
14impl FromPyObject<'_> for IpAddr {
15    fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
16        match obj.getattr(intern!(obj.py(), "packed")) {
17            Ok(packed) => {
18                if let Ok(packed) = packed.extract::<[u8; 4]>() {
19                    Ok(IpAddr::V4(Ipv4Addr::from(packed)))
20                } else if let Ok(packed) = packed.extract::<[u8; 16]>() {
21                    Ok(IpAddr::V6(Ipv6Addr::from(packed)))
22                } else {
23                    Err(PyValueError::new_err("invalid packed length"))
24                }
25            }
26            Err(_) => {
27                // We don't have a .packed attribute, so we try to construct an IP from str().
28                obj.str()?.to_cow()?.parse().map_err(PyValueError::new_err)
29            }
30        }
31    }
32}
33
34#[allow(deprecated)]
35impl ToPyObject for Ipv4Addr {
36    #[inline]
37    fn to_object(&self, py: Python<'_>) -> PyObject {
38        self.into_pyobject(py).unwrap().unbind()
39    }
40}
41
42impl<'py> IntoPyObject<'py> for Ipv4Addr {
43    type Target = PyAny;
44    type Output = Bound<'py, Self::Target>;
45    type Error = PyErr;
46
47    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
48        static IPV4_ADDRESS: GILOnceCell<Py<PyType>> = GILOnceCell::new();
49        IPV4_ADDRESS
50            .import(py, "ipaddress", "IPv4Address")?
51            .call1((u32::from_be_bytes(self.octets()),))
52    }
53}
54
55impl<'py> IntoPyObject<'py> for &Ipv4Addr {
56    type Target = PyAny;
57    type Output = Bound<'py, Self::Target>;
58    type Error = PyErr;
59
60    #[inline]
61    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
62        (*self).into_pyobject(py)
63    }
64}
65
66#[allow(deprecated)]
67impl ToPyObject for Ipv6Addr {
68    #[inline]
69    fn to_object(&self, py: Python<'_>) -> PyObject {
70        self.into_pyobject(py).unwrap().unbind()
71    }
72}
73
74impl<'py> IntoPyObject<'py> for Ipv6Addr {
75    type Target = PyAny;
76    type Output = Bound<'py, Self::Target>;
77    type Error = PyErr;
78
79    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
80        static IPV6_ADDRESS: GILOnceCell<Py<PyType>> = GILOnceCell::new();
81        IPV6_ADDRESS
82            .import(py, "ipaddress", "IPv6Address")?
83            .call1((u128::from_be_bytes(self.octets()),))
84    }
85}
86
87impl<'py> IntoPyObject<'py> for &Ipv6Addr {
88    type Target = PyAny;
89    type Output = Bound<'py, Self::Target>;
90    type Error = PyErr;
91
92    #[inline]
93    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
94        (*self).into_pyobject(py)
95    }
96}
97
98#[allow(deprecated)]
99impl ToPyObject for IpAddr {
100    #[inline]
101    fn to_object(&self, py: Python<'_>) -> PyObject {
102        self.into_pyobject(py).unwrap().unbind()
103    }
104}
105
106#[allow(deprecated)]
107impl IntoPy<PyObject> for IpAddr {
108    #[inline]
109    fn into_py(self, py: Python<'_>) -> PyObject {
110        self.into_pyobject(py).unwrap().unbind()
111    }
112}
113
114impl<'py> IntoPyObject<'py> for IpAddr {
115    type Target = PyAny;
116    type Output = Bound<'py, Self::Target>;
117    type Error = PyErr;
118
119    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
120        match self {
121            IpAddr::V4(ip) => ip.into_pyobject(py),
122            IpAddr::V6(ip) => ip.into_pyobject(py),
123        }
124    }
125}
126
127impl<'py> IntoPyObject<'py> for &IpAddr {
128    type Target = PyAny;
129    type Output = Bound<'py, Self::Target>;
130    type Error = PyErr;
131
132    #[inline]
133    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
134        (*self).into_pyobject(py)
135    }
136}
137
138#[cfg(test)]
139mod test_ipaddr {
140    use std::str::FromStr;
141
142    use crate::types::PyString;
143
144    use super::*;
145
146    #[test]
147    fn test_roundtrip() {
148        Python::with_gil(|py| {
149            fn roundtrip(py: Python<'_>, ip: &str) {
150                let ip = IpAddr::from_str(ip).unwrap();
151                let py_cls = if ip.is_ipv4() {
152                    "IPv4Address"
153                } else {
154                    "IPv6Address"
155                };
156
157                let pyobj = ip.into_pyobject(py).unwrap();
158                let repr = pyobj.repr().unwrap();
159                let repr = repr.to_string_lossy();
160                assert_eq!(repr, format!("{}('{}')", py_cls, ip));
161
162                let ip2: IpAddr = pyobj.extract().unwrap();
163                assert_eq!(ip, ip2);
164            }
165            roundtrip(py, "127.0.0.1");
166            roundtrip(py, "::1");
167            roundtrip(py, "0.0.0.0");
168        });
169    }
170
171    #[test]
172    fn test_from_pystring() {
173        Python::with_gil(|py| {
174            let py_str = PyString::new(py, "0:0:0:0:0:0:0:1");
175            let ip: IpAddr = py_str.extract().unwrap();
176            assert_eq!(ip, IpAddr::from_str("::1").unwrap());
177
178            let py_str = PyString::new(py, "invalid");
179            assert!(py_str.extract::<IpAddr>().is_err());
180        });
181    }
182}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here