pyo3/conversions/std/
ipaddr.rs1use 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 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}