1use super::{PyDict, PyDictRef, PyGenericAlias, PyList, PyTuple, PyType, PyTypeRef};
2use crate::{
3 atomic_func,
4 class::PyClassImpl,
5 convert::ToPyObject,
6 function::{ArgMapping, OptionalArg, PyComparisonValue},
7 object::{Traverse, TraverseFn},
8 protocol::{PyMapping, PyMappingMethods, PyNumberMethods, PySequenceMethods},
9 types::{
10 AsMapping, AsNumber, AsSequence, Comparable, Constructor, Iterable, PyComparisonOp,
11 Representable,
12 },
13 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
14};
15use once_cell::sync::Lazy;
16
17#[pyclass(module = false, name = "mappingproxy", traverse)]
18#[derive(Debug)]
19pub struct PyMappingProxy {
20 mapping: MappingProxyInner,
21}
22
23#[derive(Debug)]
24enum MappingProxyInner {
25 Class(PyTypeRef),
26 Mapping(ArgMapping),
27}
28
29unsafe impl Traverse for MappingProxyInner {
30 fn traverse(&self, tracer_fn: &mut TraverseFn) {
31 match self {
32 MappingProxyInner::Class(ref r) => r.traverse(tracer_fn),
33 MappingProxyInner::Mapping(ref arg) => arg.traverse(tracer_fn),
34 }
35 }
36}
37
38impl PyPayload for PyMappingProxy {
39 fn class(ctx: &Context) -> &'static Py<PyType> {
40 ctx.types.mappingproxy_type
41 }
42}
43
44impl From<PyTypeRef> for PyMappingProxy {
45 fn from(dict: PyTypeRef) -> Self {
46 Self {
47 mapping: MappingProxyInner::Class(dict),
48 }
49 }
50}
51
52impl From<PyDictRef> for PyMappingProxy {
53 fn from(dict: PyDictRef) -> Self {
54 Self {
55 mapping: MappingProxyInner::Mapping(ArgMapping::from_dict_exact(dict)),
56 }
57 }
58}
59
60impl Constructor for PyMappingProxy {
61 type Args = PyObjectRef;
62
63 fn py_new(cls: PyTypeRef, mapping: Self::Args, vm: &VirtualMachine) -> PyResult {
64 if let Some(methods) = PyMapping::find_methods(&mapping) {
65 if mapping.payload_if_subclass::<PyList>(vm).is_none()
66 && mapping.payload_if_subclass::<PyTuple>(vm).is_none()
67 {
68 return Self {
69 mapping: MappingProxyInner::Mapping(ArgMapping::with_methods(
70 mapping,
71 unsafe { methods.borrow_static() },
72 )),
73 }
74 .into_ref_with_type(vm, cls)
75 .map(Into::into);
76 }
77 }
78 Err(vm.new_type_error(format!(
79 "mappingproxy() argument must be a mapping, not {}",
80 mapping.class()
81 )))
82 }
83}
84
85#[pyclass(with(
86 AsMapping,
87 Iterable,
88 Constructor,
89 AsSequence,
90 Comparable,
91 AsNumber,
92 Representable
93))]
94impl PyMappingProxy {
95 fn get_inner(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
96 match &self.mapping {
97 MappingProxyInner::Class(class) => Ok(key
98 .as_interned_str(vm)
99 .and_then(|key| class.attributes.read().get(key).cloned())),
100 MappingProxyInner::Mapping(mapping) => mapping.mapping().subscript(&*key, vm).map(Some),
101 }
102 }
103
104 #[pymethod]
105 fn get(
106 &self,
107 key: PyObjectRef,
108 default: OptionalArg,
109 vm: &VirtualMachine,
110 ) -> PyResult<Option<PyObjectRef>> {
111 let obj = self.to_object(vm)?;
112 Ok(Some(vm.call_method(
113 &obj,
114 "get",
115 (key, default.unwrap_or_none(vm)),
116 )?))
117 }
118
119 #[pymethod(magic)]
120 pub fn getitem(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
121 self.get_inner(key.clone(), vm)?
122 .ok_or_else(|| vm.new_key_error(key))
123 }
124
125 fn _contains(&self, key: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
126 match &self.mapping {
127 MappingProxyInner::Class(class) => Ok(key
128 .as_interned_str(vm)
129 .map_or(false, |key| class.attributes.read().contains_key(key))),
130 MappingProxyInner::Mapping(mapping) => mapping.to_sequence().contains(key, vm),
131 }
132 }
133
134 #[pymethod(magic)]
135 pub fn contains(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
136 self._contains(&key, vm)
137 }
138
139 fn to_object(&self, vm: &VirtualMachine) -> PyResult {
140 Ok(match &self.mapping {
141 MappingProxyInner::Mapping(d) => d.as_ref().to_owned(),
142 MappingProxyInner::Class(c) => {
143 PyDict::from_attributes(c.attributes.read().clone(), vm)?.to_pyobject(vm)
144 }
145 })
146 }
147
148 #[pymethod]
149 pub fn items(&self, vm: &VirtualMachine) -> PyResult {
150 let obj = self.to_object(vm)?;
151 vm.call_method(&obj, identifier!(vm, items).as_str(), ())
152 }
153 #[pymethod]
154 pub fn keys(&self, vm: &VirtualMachine) -> PyResult {
155 let obj = self.to_object(vm)?;
156 vm.call_method(&obj, identifier!(vm, keys).as_str(), ())
157 }
158 #[pymethod]
159 pub fn values(&self, vm: &VirtualMachine) -> PyResult {
160 let obj = self.to_object(vm)?;
161 vm.call_method(&obj, identifier!(vm, values).as_str(), ())
162 }
163 #[pymethod]
164 pub fn copy(&self, vm: &VirtualMachine) -> PyResult {
165 match &self.mapping {
166 MappingProxyInner::Mapping(d) => vm.call_method(d, identifier!(vm, copy).as_str(), ()),
167 MappingProxyInner::Class(c) => {
168 Ok(PyDict::from_attributes(c.attributes.read().clone(), vm)?.to_pyobject(vm))
169 }
170 }
171 }
172
173 #[pyclassmethod(magic)]
174 fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
175 PyGenericAlias::new(cls, args, vm)
176 }
177
178 #[pymethod(magic)]
179 fn len(&self, vm: &VirtualMachine) -> PyResult<usize> {
180 let obj = self.to_object(vm)?;
181 obj.length(vm)
182 }
183
184 #[pymethod(magic)]
185 fn reversed(&self, vm: &VirtualMachine) -> PyResult {
186 vm.call_method(
187 self.to_object(vm)?.as_object(),
188 identifier!(vm, __reversed__).as_str(),
189 (),
190 )
191 }
192
193 #[pymethod(magic)]
194 fn ior(&self, _args: PyObjectRef, vm: &VirtualMachine) -> PyResult {
195 Err(vm.new_type_error(format!(
196 "\"'|=' is not supported by {}; use '|' instead\"",
197 Self::class(&vm.ctx)
198 )))
199 }
200
201 #[pymethod(name = "__ror__")]
202 #[pymethod(magic)]
203 fn or(&self, args: PyObjectRef, vm: &VirtualMachine) -> PyResult {
204 vm._or(self.copy(vm)?.as_ref(), args.as_ref())
205 }
206}
207
208impl Comparable for PyMappingProxy {
209 fn cmp(
210 zelf: &Py<Self>,
211 other: &PyObject,
212 op: PyComparisonOp,
213 vm: &VirtualMachine,
214 ) -> PyResult<PyComparisonValue> {
215 let obj = zelf.to_object(vm)?;
216 Ok(PyComparisonValue::Implemented(
217 obj.rich_compare_bool(other, op, vm)?,
218 ))
219 }
220}
221
222impl AsMapping for PyMappingProxy {
223 fn as_mapping() -> &'static PyMappingMethods {
224 static AS_MAPPING: Lazy<PyMappingMethods> = Lazy::new(|| PyMappingMethods {
225 length: atomic_func!(|mapping, vm| PyMappingProxy::mapping_downcast(mapping).len(vm)),
226 subscript: atomic_func!(|mapping, needle, vm| {
227 PyMappingProxy::mapping_downcast(mapping).getitem(needle.to_owned(), vm)
228 }),
229 ..PyMappingMethods::NOT_IMPLEMENTED
230 });
231 &AS_MAPPING
232 }
233}
234
235impl AsSequence for PyMappingProxy {
236 fn as_sequence() -> &'static PySequenceMethods {
237 static AS_SEQUENCE: Lazy<PySequenceMethods> = Lazy::new(|| PySequenceMethods {
238 contains: atomic_func!(
239 |seq, target, vm| PyMappingProxy::sequence_downcast(seq)._contains(target, vm)
240 ),
241 ..PySequenceMethods::NOT_IMPLEMENTED
242 });
243 &AS_SEQUENCE
244 }
245}
246
247impl AsNumber for PyMappingProxy {
248 fn as_number() -> &'static PyNumberMethods {
249 static AS_NUMBER: PyNumberMethods = PyNumberMethods {
250 or: Some(|a, b, vm| {
251 if let Some(a) = a.downcast_ref::<PyMappingProxy>() {
252 a.or(b.to_pyobject(vm), vm)
253 } else {
254 Ok(vm.ctx.not_implemented())
255 }
256 }),
257 inplace_or: Some(|a, b, vm| {
258 if let Some(a) = a.downcast_ref::<PyMappingProxy>() {
259 a.ior(b.to_pyobject(vm), vm)
260 } else {
261 Ok(vm.ctx.not_implemented())
262 }
263 }),
264 ..PyNumberMethods::NOT_IMPLEMENTED
265 };
266 &AS_NUMBER
267 }
268}
269
270impl Iterable for PyMappingProxy {
271 fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
272 let obj = zelf.to_object(vm)?;
273 let iter = obj.get_iter(vm)?;
274 Ok(iter.into())
275 }
276}
277
278impl Representable for PyMappingProxy {
279 #[inline]
280 fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
281 let obj = zelf.to_object(vm)?;
282 Ok(format!("mappingproxy({})", obj.repr(vm)?))
283 }
284}
285
286pub fn init(context: &Context) {
287 PyMappingProxy::extend_class(context, context.types.mappingproxy_type)
288}