diff --git a/tests/snippets/mappingproxy.py b/tests/snippets/mappingproxy.py new file mode 100644 index 0000000000..624bf6d5f2 --- /dev/null +++ b/tests/snippets/mappingproxy.py @@ -0,0 +1,18 @@ +from testutils import assertRaises + +class A(dict): + def a(): + pass + + def b(): + pass + + +assert A.__dict__['a'] == A.a +with assertRaises(KeyError): + A.__dict__['not_here'] + +assert 'b' in A.__dict__ +assert 'c' not in A.__dict__ + +assert '__dict__' in A.__dict__ diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index bfe89fd15a..c9401d8c44 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -20,6 +20,7 @@ pub mod objint; pub mod objiter; pub mod objlist; pub mod objmap; +pub mod objmappingproxy; pub mod objmemory; pub mod objmodule; pub mod objnone; diff --git a/vm/src/obj/objmappingproxy.rs b/vm/src/obj/objmappingproxy.rs new file mode 100644 index 0000000000..c46591105b --- /dev/null +++ b/vm/src/obj/objmappingproxy.rs @@ -0,0 +1,42 @@ +use super::objstr::PyStringRef; +use super::objtype::{self, PyClassRef}; +use crate::pyobject::{PyClassImpl, PyContext, PyRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; + +#[pyclass] +#[derive(Debug)] +pub struct PyMappingProxy { + class: PyClassRef, +} + +pub type PyMappingProxyRef = PyRef; + +impl PyValue for PyMappingProxy { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.mappingproxy_type.clone() + } +} + +#[pyimpl] +impl PyMappingProxy { + pub fn new(class: PyClassRef) -> PyMappingProxy { + PyMappingProxy { class } + } + + #[pymethod(name = "__getitem__")] + pub fn getitem(&self, key: PyStringRef, vm: &VirtualMachine) -> PyResult { + if let Some(value) = objtype::class_get_attr(&self.class, key.as_str()) { + return Ok(value); + } + Err(vm.new_key_error(format!("Key not found: {}", key))) + } + + #[pymethod(name = "__contains__")] + pub fn contains(&self, attr: PyStringRef, _vm: &VirtualMachine) -> bool { + objtype::class_has_attr(&self.class, attr.as_str()) + } +} + +pub fn init(context: &PyContext) { + PyMappingProxy::extend_class(context, &context.mappingproxy_type) +} diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 47cfa008dd..31e52fc990 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -213,7 +213,7 @@ fn object_dict_setter( vm: &VirtualMachine, ) -> PyResult { Err(vm.new_not_implemented_error( - "Setting __dict__ attribute on am object isn't yet implemented".to_string(), + "Setting __dict__ attribute on an object isn't yet implemented".to_string(), )) } diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index c14e512301..c66e57354b 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -11,6 +11,7 @@ use crate::vm::VirtualMachine; use super::objdict::PyDictRef; use super::objlist::PyList; +use super::objmappingproxy::PyMappingProxy; use super::objproperty::PropertyBuilder; use super::objstr::PyStringRef; use super::objtuple::PyTuple; @@ -196,6 +197,11 @@ pub fn init(ctx: &PyContext) { extend_class!(&ctx, &ctx.type_type, { "__call__" => ctx.new_rustfunc(type_call), + "__dict__" => + PropertyBuilder::new(ctx) + .add_getter(type_dict) + .add_setter(type_dict_setter) + .create(), "__new__" => ctx.new_rustfunc(type_new), "__mro__" => PropertyBuilder::new(ctx) @@ -273,6 +279,16 @@ pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMach Ok(obj) } +fn type_dict(class: PyClassRef, _vm: &VirtualMachine) -> PyMappingProxy { + PyMappingProxy::new(class) +} + +fn type_dict_setter(_instance: PyClassRef, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_not_implemented_error( + "Setting __dict__ attribute on a type isn't yet implemented".to_string(), + )) +} + // This is the internal get_attr implementation for fast lookup on a class. pub fn class_get_attr(class: &PyClassRef, attr_name: &str) -> Option { if let Some(item) = class.attributes.borrow().get(attr_name).cloned() { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 89e5c8c394..454a1605fa 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -37,6 +37,7 @@ use crate::obj::objint::{self, PyInt, PyIntRef}; use crate::obj::objiter; use crate::obj::objlist::{self, PyList}; use crate::obj::objmap; +use crate::obj::objmappingproxy; use crate::obj::objmemory; use crate::obj::objmodule::{self, PyModule}; use crate::obj::objnone::{self, PyNone, PyNoneRef}; @@ -160,6 +161,7 @@ pub struct PyContext { pub bound_method_type: PyClassRef, pub weakref_type: PyClassRef, pub weakproxy_type: PyClassRef, + pub mappingproxy_type: PyClassRef, pub object: PyClassRef, pub exceptions: exceptions::ExceptionZoo, } @@ -292,6 +294,7 @@ impl PyContext { let range_type = create_type("range", &type_type, &object_type); let rangeiterator_type = create_type("range_iterator", &type_type, &object_type); let slice_type = create_type("slice", &type_type, &object_type); + let mappingproxy_type = create_type("mappingproxy", &type_type, &object_type); let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type); fn create_object(payload: T, cls: &PyClassRef) -> PyRef { @@ -358,6 +361,7 @@ impl PyContext { function_type, builtin_function_or_method_type, super_type, + mappingproxy_type, property_type, readonly_property_type, generator_type, @@ -403,6 +407,7 @@ impl PyContext { objweakproxy::init(&context); objnone::init(&context); objmodule::init(&context); + objmappingproxy::init(&context); exceptions::init(&context); context }