pyo3/conversions/std/
ipaddr.rs1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
2
3use crate::conversion::IntoPyObject;
4use crate::exceptions::PyValueError;
5use crate::sync::PyOnceLock;
6use crate::types::any::PyAnyMethods;
7use crate::types::string::PyStringMethods;
8use crate::types::PyType;
9use crate::{intern, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, Python};
10
11impl FromPyObject<'_, '_> for IpAddr {
12 type Error = PyErr;
13
14 fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
15 match obj.getattr(intern!(obj.py(), "packed")) {
16 Ok(packed) => {
17 if let Ok(packed) = packed.extract::<[u8; 4]>() {
18 Ok(IpAddr::V4(Ipv4Addr::from(packed)))
19 } else if let Ok(packed) = packed.extract::<[u8; 16]>() {
20 Ok(IpAddr::V6(Ipv6Addr::from(packed)))
21 } else {
22 Err(PyValueError::new_err("invalid packed length"))
23 }
24 }
25 Err(_) => {
26 obj.str()?.to_cow()?.parse().map_err(PyValueError::new_err)
28 }
29 }
30 }
31}
32
33impl<'py> IntoPyObject<'py> for Ipv4Addr {
34 type Target = PyAny;
35 type Output = Bound<'py, Self::Target>;
36 type Error = PyErr;
37
38 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
39 static IPV4_ADDRESS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
40 IPV4_ADDRESS
41 .import(py, "ipaddress", "IPv4Address")?
42 .call1((u32::from_be_bytes(self.octets()),))
43 }
44}
45
46impl<'py> IntoPyObject<'py> for &Ipv4Addr {
47 type Target = PyAny;
48 type Output = Bound<'py, Self::Target>;
49 type Error = PyErr;
50
51 #[inline]
52 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
53 (*self).into_pyobject(py)
54 }
55}
56
57impl<'py> IntoPyObject<'py> for Ipv6Addr {
58 type Target = PyAny;
59 type Output = Bound<'py, Self::Target>;
60 type Error = PyErr;
61
62 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
63 static IPV6_ADDRESS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
64 IPV6_ADDRESS
65 .import(py, "ipaddress", "IPv6Address")?
66 .call1((u128::from_be_bytes(self.octets()),))
67 }
68}
69
70impl<'py> IntoPyObject<'py> for &Ipv6Addr {
71 type Target = PyAny;
72 type Output = Bound<'py, Self::Target>;
73 type Error = PyErr;
74
75 #[inline]
76 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
77 (*self).into_pyobject(py)
78 }
79}
80
81impl<'py> IntoPyObject<'py> for IpAddr {
82 type Target = PyAny;
83 type Output = Bound<'py, Self::Target>;
84 type Error = PyErr;
85
86 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
87 match self {
88 IpAddr::V4(ip) => ip.into_pyobject(py),
89 IpAddr::V6(ip) => ip.into_pyobject(py),
90 }
91 }
92}
93
94impl<'py> IntoPyObject<'py> for &IpAddr {
95 type Target = PyAny;
96 type Output = Bound<'py, Self::Target>;
97 type Error = PyErr;
98
99 #[inline]
100 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
101 (*self).into_pyobject(py)
102 }
103}
104
105#[cfg(test)]
106mod test_ipaddr {
107 use std::str::FromStr;
108
109 use crate::types::PyString;
110
111 use super::*;
112
113 #[test]
114 fn test_roundtrip() {
115 Python::attach(|py| {
116 fn roundtrip(py: Python<'_>, ip: &str) {
117 let ip = IpAddr::from_str(ip).unwrap();
118 let py_cls = if ip.is_ipv4() {
119 "IPv4Address"
120 } else {
121 "IPv6Address"
122 };
123
124 let pyobj = ip.into_pyobject(py).unwrap();
125 let repr = pyobj.repr().unwrap();
126 let repr = repr.to_string_lossy();
127 assert_eq!(repr, format!("{py_cls}('{ip}')"));
128
129 let ip2: IpAddr = pyobj.extract().unwrap();
130 assert_eq!(ip, ip2);
131 }
132 roundtrip(py, "127.0.0.1");
133 roundtrip(py, "::1");
134 roundtrip(py, "0.0.0.0");
135 });
136 }
137
138 #[test]
139 fn test_from_pystring() {
140 Python::attach(|py| {
141 let py_str = PyString::new(py, "0:0:0:0:0:0:0:1");
142 let ip: IpAddr = py_str.extract().unwrap();
143 assert_eq!(ip, IpAddr::from_str("::1").unwrap());
144
145 let py_str = PyString::new(py, "invalid");
146 assert!(py_str.extract::<IpAddr>().is_err());
147 });
148 }
149}