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