cpython/
objectprotocol.rs

1// Copyright (c) 2015 Daniel Grunwald
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19use std::cmp::Ordering;
20use std::fmt;
21
22use crate::conversion::ToPyObject;
23use crate::err::{self, PyErr, PyResult};
24use crate::ffi;
25use crate::objects::{PyDict, PyObject, PyString, PyTuple};
26use crate::python::{Python, PythonObject, ToPythonPointer};
27
28mod number;
29
30pub use self::number::NumberProtocol;
31
32/// Trait that contains methods
33pub trait ObjectProtocol: PythonObject {
34    /// Determines whether this object has the given attribute.
35    /// This is equivalent to the Python expression 'hasattr(self, attr_name)'.
36    #[inline]
37    fn hasattr<N>(&self, py: Python, attr_name: N) -> PyResult<bool>
38    where
39        N: ToPyObject,
40    {
41        attr_name.with_borrowed_ptr(py, |attr_name| unsafe {
42            Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name) != 0)
43        })
44    }
45
46    /// Retrieves an attribute value.
47    /// This is equivalent to the Python expression 'self.attr_name'.
48    #[inline]
49    fn getattr<N>(&self, py: Python, attr_name: N) -> PyResult<PyObject>
50    where
51        N: ToPyObject,
52    {
53        attr_name.with_borrowed_ptr(py, |attr_name| unsafe {
54            err::result_from_owned_ptr(py, ffi::PyObject_GetAttr(self.as_ptr(), attr_name))
55        })
56    }
57
58    /// Sets an attribute value.
59    /// This is equivalent to the Python expression 'self.attr_name = value'.
60    #[inline]
61    fn setattr<N, V>(&self, py: Python, attr_name: N, value: V) -> PyResult<()>
62    where
63        N: ToPyObject,
64        V: ToPyObject,
65    {
66        attr_name.with_borrowed_ptr(py, move |attr_name| {
67            value.with_borrowed_ptr(py, |value| unsafe {
68                err::error_on_minusone(py, ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value))
69            })
70        })
71    }
72
73    /// Deletes an attribute.
74    /// This is equivalent to the Python expression 'del self.attr_name'.
75    #[inline]
76    fn delattr<N>(&self, py: Python, attr_name: N) -> PyResult<()>
77    where
78        N: ToPyObject,
79    {
80        attr_name.with_borrowed_ptr(py, |attr_name| unsafe {
81            err::error_on_minusone(py, ffi::PyObject_DelAttr(self.as_ptr(), attr_name))
82        })
83    }
84
85    /// Compares two Python objects.
86    ///
87    /// On Python 2, this is equivalent to the Python expression 'cmp(self, other)'.
88    ///
89    /// On Python 3, this is equivalent to:
90    /// ```ignore
91    /// if self == other:
92    ///     return Equal
93    /// elif a < b:
94    ///     return Less
95    /// elif a > b:
96    ///     return Greater
97    /// else:
98    ///     raise TypeError("ObjectProtocol::compare(): All comparisons returned false")
99    /// ```
100    fn compare<O>(&self, py: Python, other: O) -> PyResult<Ordering>
101    where
102        O: ToPyObject,
103    {
104        #[cfg(feature = "python27-sys")]
105        unsafe fn do_compare(
106            py: Python,
107            a: *mut ffi::PyObject,
108            b: *mut ffi::PyObject,
109        ) -> PyResult<Ordering> {
110            let mut result = -1;
111            err::error_on_minusone(py, ffi::PyObject_Cmp(a, b, &mut result))?;
112            Ok(result.cmp(&0))
113        }
114
115        #[cfg(feature = "python3-sys")]
116        unsafe fn do_compare(
117            py: Python,
118            a: *mut ffi::PyObject,
119            b: *mut ffi::PyObject,
120        ) -> PyResult<Ordering> {
121            let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_EQ);
122            if result == 1 {
123                return Ok(Ordering::Equal);
124            } else if result < 0 {
125                return Err(PyErr::fetch(py));
126            }
127            let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_LT);
128            if result == 1 {
129                return Ok(Ordering::Less);
130            } else if result < 0 {
131                return Err(PyErr::fetch(py));
132            }
133            let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_GT);
134            if result == 1 {
135                return Ok(Ordering::Greater);
136            } else if result < 0 {
137                return Err(PyErr::fetch(py));
138            }
139            Err(PyErr::new::<crate::exc::TypeError, _>(
140                py,
141                "ObjectProtocol::compare(): All comparisons returned false",
142            ))
143        }
144
145        other.with_borrowed_ptr(py, |other| unsafe { do_compare(py, self.as_ptr(), other) })
146    }
147
148    /// Compares two Python objects.
149    ///
150    /// Depending on the value of `compare_op`, equivalent to one of the following Python expressions:
151    ///   * CompareOp::Eq: `self == other`
152    ///   * CompareOp::Ne: `self != other`
153    ///   * CompareOp::Lt: `self < other`
154    ///   * CompareOp::Le: `self <= other`
155    ///   * CompareOp::Gt: `self > other`
156    ///   * CompareOp::Ge: `self >= other`
157    fn rich_compare<O>(
158        &self,
159        py: Python,
160        other: O,
161        compare_op: crate::CompareOp,
162    ) -> PyResult<PyObject>
163    where
164        O: ToPyObject,
165    {
166        other.with_borrowed_ptr(py, |other| unsafe {
167            err::result_cast_from_owned_ptr(
168                py,
169                ffi::PyObject_RichCompare(self.as_ptr(), other, compare_op as libc::c_int),
170            )
171        })
172    }
173
174    /// Compute the string representation of self.
175    /// This is equivalent to the Python expression 'repr(self)'.
176    #[inline]
177    fn repr(&self, py: Python) -> PyResult<PyString> {
178        unsafe { err::result_cast_from_owned_ptr(py, ffi::PyObject_Repr(self.as_ptr())) }
179    }
180
181    /// Compute the string representation of self.
182    /// This is equivalent to the Python expression 'str(self)'.
183    #[inline]
184    fn str(&self, py: Python) -> PyResult<PyString> {
185        unsafe { err::result_cast_from_owned_ptr(py, ffi::PyObject_Str(self.as_ptr())) }
186    }
187
188    /// Compute the unicode string representation of self.
189    /// This is equivalent to the Python expression 'unistr(self)'.
190    #[inline]
191    #[cfg(feature = "python27-sys")]
192    fn unistr(&self, py: Python) -> PyResult<crate::objects::PyUnicode> {
193        unsafe { err::result_cast_from_owned_ptr(py, ffi::PyObject_Unicode(self.as_ptr())) }
194    }
195
196    /// Determines whether this object is callable.
197    #[inline]
198    fn is_callable(&self, _py: Python) -> bool {
199        unsafe { ffi::PyCallable_Check(self.as_ptr()) != 0 }
200    }
201
202    /// Calls the object.
203    /// This is equivalent to the Python expression: 'self(*args, **kwargs)'
204    ///
205    /// `args` should be a value that, when converted to Python, results in a tuple.
206    /// For this purpose, you can use:
207    ///  * `cpython::NoArgs` when calling a method without any arguments
208    ///  * otherwise, a Rust tuple with 1 or more elements
209    #[inline]
210    fn call<A>(&self, py: Python, args: A, kwargs: Option<&PyDict>) -> PyResult<PyObject>
211    where
212        A: ToPyObject<ObjectType = PyTuple>,
213    {
214        args.with_borrowed_ptr(py, |args| unsafe {
215            err::result_from_owned_ptr(py, ffi::PyObject_Call(self.as_ptr(), args, kwargs.as_ptr()))
216        })
217    }
218
219    /// Calls a method on the object.
220    /// This is equivalent to the Python expression: 'self.name(*args, **kwargs)'
221    ///
222    /// `args` should be a value that, when converted to Python, results in a tuple.
223    /// For this purpose, you can use:
224    ///  * `cpython::NoArgs` when calling a method without any arguments
225    ///  * otherwise, a Rust tuple with 1 or more elements
226    ///
227    /// # Example
228    /// ```no_run
229    /// use cpython::{NoArgs, ObjectProtocol};
230    /// # use cpython::Python;
231    /// # let gil = Python::acquire_gil();
232    /// # let py = gil.python();
233    /// # let obj = py.None();
234    /// // Call method without arguments:
235    /// let value = obj.call_method(py, "method0", NoArgs, None).unwrap();
236    /// // Call method with a single argument:
237    /// obj.call_method(py, "method1", (true,), None).unwrap();
238    /// ```
239    #[inline]
240    fn call_method<A>(
241        &self,
242        py: Python,
243        name: &str,
244        args: A,
245        kwargs: Option<&PyDict>,
246    ) -> PyResult<PyObject>
247    where
248        A: ToPyObject<ObjectType = PyTuple>,
249    {
250        self.getattr(py, name)?.call(py, args, kwargs)
251    }
252
253    /// Retrieves the hash code of the object.
254    /// This is equivalent to the Python expression: 'hash(self)'
255    #[inline]
256    fn hash(&self, py: Python) -> PyResult<crate::Py_hash_t> {
257        let v = unsafe { ffi::PyObject_Hash(self.as_ptr()) };
258        if v == -1 {
259            Err(PyErr::fetch(py))
260        } else {
261            Ok(v)
262        }
263    }
264
265    /// Returns whether the object is considered to be true.
266    /// This is equivalent to the Python expression: 'not not self'
267    #[inline]
268    fn is_true(&self, py: Python) -> PyResult<bool> {
269        let v = unsafe { ffi::PyObject_IsTrue(self.as_ptr()) };
270        if v == -1 {
271            Err(PyErr::fetch(py))
272        } else {
273            Ok(v != 0)
274        }
275    }
276
277    /// Returns the length of the sequence or mapping.
278    /// This is equivalent to the Python expression: 'len(self)'
279    #[inline]
280    fn len(&self, py: Python) -> PyResult<usize> {
281        let v = unsafe { ffi::PyObject_Size(self.as_ptr()) };
282        if v == -1 {
283            Err(PyErr::fetch(py))
284        } else {
285            Ok(v as usize)
286        }
287    }
288
289    /// This is equivalent to the Python expression: 'self[key]'
290    #[inline]
291    fn get_item<K>(&self, py: Python, key: K) -> PyResult<PyObject>
292    where
293        K: ToPyObject,
294    {
295        key.with_borrowed_ptr(py, |key| unsafe {
296            err::result_from_owned_ptr(py, ffi::PyObject_GetItem(self.as_ptr(), key))
297        })
298    }
299
300    /// Sets an item value.
301    /// This is equivalent to the Python expression 'self[key] = value'.
302    #[inline]
303    fn set_item<K, V>(&self, py: Python, key: K, value: V) -> PyResult<()>
304    where
305        K: ToPyObject,
306        V: ToPyObject,
307    {
308        key.with_borrowed_ptr(py, move |key| {
309            value.with_borrowed_ptr(py, |value| unsafe {
310                err::error_on_minusone(py, ffi::PyObject_SetItem(self.as_ptr(), key, value))
311            })
312        })
313    }
314
315    /// Deletes an item.
316    /// This is equivalent to the Python expression 'del self[key]'.
317    #[inline]
318    fn del_item<K>(&self, py: Python, key: K) -> PyResult<()>
319    where
320        K: ToPyObject,
321    {
322        key.with_borrowed_ptr(py, |key| unsafe {
323            err::error_on_minusone(py, ffi::PyObject_DelItem(self.as_ptr(), key))
324        })
325    }
326
327    /// Takes an object and returns an iterator for it.
328    /// This is typically a new iterator but if the argument
329    /// is an iterator, this returns itself.
330    #[inline]
331    fn iter<'p>(&self, py: Python<'p>) -> PyResult<crate::objects::PyIterator<'p>> {
332        let obj = unsafe { err::result_from_owned_ptr(py, ffi::PyObject_GetIter(self.as_ptr())) }?;
333        Ok(crate::objects::PyIterator::from_object(py, obj)?)
334    }
335}
336
337impl ObjectProtocol for PyObject {}
338
339impl fmt::Debug for PyObject {
340    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
341        // TODO: we shouldn't use fmt::Error when repr() fails
342        let gil_guard = Python::acquire_gil();
343        let py = gil_guard.python();
344        let repr_obj = self.repr(py).map_err(|_| fmt::Error)?;
345        f.write_str(&repr_obj.to_string_lossy(py))
346    }
347}
348
349impl fmt::Display for PyObject {
350    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
351        // TODO: we shouldn't use fmt::Error when str() fails
352        let gil_guard = Python::acquire_gil();
353        let py = gil_guard.python();
354        let str_obj = self.str(py).map_err(|_| fmt::Error)?;
355        f.write_str(&str_obj.to_string_lossy(py))
356    }
357}
358
359#[cfg(test)]
360mod test {
361    use super::ObjectProtocol;
362    use crate::conversion::ToPyObject;
363    use crate::objects::{PyList, PyTuple};
364    use crate::python::{Python, PythonObject};
365
366    #[test]
367    fn test_debug_string() {
368        let gil = Python::acquire_gil();
369        let py = gil.python();
370        let v = "Hello\n".to_py_object(py).into_object();
371        assert_eq!(format!("{:?}", v), "'Hello\\n'");
372    }
373
374    #[test]
375    fn test_display_string() {
376        let gil = Python::acquire_gil();
377        let py = gil.python();
378        let v = "Hello\n".to_py_object(py).into_object();
379        assert_eq!(format!("{}", v), "Hello\n");
380    }
381
382    #[test]
383    fn test_compare() {
384        use std::cmp::Ordering;
385        let gil = Python::acquire_gil();
386        let py = gil.python();
387        let one = 1i32.to_py_object(py).into_object();
388        assert_eq!(one.compare(py, 1).unwrap(), Ordering::Equal);
389        assert_eq!(one.compare(py, 2).unwrap(), Ordering::Less);
390        assert_eq!(one.compare(py, 0).unwrap(), Ordering::Greater);
391    }
392}