diff --git a/enaml/src/dynamicscope.cpp b/enaml/src/dynamicscope.cpp index 7a1152cd3..f26478386 100644 --- a/enaml/src/dynamicscope.cpp +++ b/enaml/src/dynamicscope.cpp @@ -784,9 +784,105 @@ DynamicScope_contains( DynamicScope* self, PyObject* key ) return test_dynamic_attr( self->owner, key ); } +PyObject* +_DynamicScope_dict( DynamicScope* self) +{ + cppy::ptr items( PyDict_New() ); + if ( !items ) + return 0; + + if ( PyDict_Update( items.get(), self->f_builtins ) ) + return 0; + + if ( PyDict_Update( items.get(), self->f_globals ) ) + return 0; + + if ( PyDict_Update( items.get(), self->f_locals ) ) + return 0; + + if ( self->f_writes && PyDict_Update( items.get(), self->f_writes ) ) + return 0; + + if ( self->change && PyDict_SetItemString( items.get(), "change", cppy::incref(self->change) ) ) + return 0; + + if ( PyDict_SetItemString( items.get(), "self", cppy::incref(self->owner) ) ) + return 0; + + return items.release(); +} + + +PyObject* +DynamicScope_items( DynamicScope* self) +{ + cppy::ptr dict( _DynamicScope_dict( self ) ); + if ( !dict ) + return 0; + return PyDict_Items( dict.get() ); +} + + +PyObject* +DynamicScope_keys( DynamicScope* self) +{ + cppy::ptr dict( _DynamicScope_dict( self ) ); + if ( !dict ) + return 0; + return PyDict_Keys( dict.get() ); +} + + +PyObject* +DynamicScope_values( DynamicScope* self) +{ + cppy::ptr dict( _DynamicScope_dict( self ) ); + if ( !dict ) + return 0; + return PyDict_Values( dict.get() ); +} + + +PyObject* +DynamicScope_iter( DynamicScope* self) +{ + cppy::ptr dict( _DynamicScope_dict( self ) ); + if ( !dict ) + return 0; + return dict.iter(); +} + + +PyObject* +DynamicScope_update( DynamicScope* self, PyObject* arg ) +{ + if( !PyMapping_Check( arg ) ) + return cppy::type_error( arg, "mapping" ); + + cppy::ptr items( cppy::incref( arg ) ); + cppy::ptr iter( items.iter() ); + if ( !iter ) + return 0; + + cppy::ptr key; + while ( (key = iter.next()) ) + { + cppy::ptr value ( PyObject_GetItem( items.get(), key.get() ) ); + if ( !value ) + return 0; + if ( DynamicScope_setitem( self, key.get(), value.get() ) ) + return 0; + } + + Py_RETURN_NONE; +} static PyMethodDef DynamicScope_methods[] = { {"get", reinterpret_cast(DynamicScope_get), METH_VARARGS, ""}, + {"items", reinterpret_cast(DynamicScope_items), METH_NOARGS, ""}, + {"keys", reinterpret_cast(DynamicScope_keys), METH_NOARGS, ""}, + {"values", reinterpret_cast(DynamicScope_values), METH_NOARGS, ""}, + {"update", reinterpret_cast(DynamicScope_update), METH_O, ""}, { 0 } // Sentinel }; @@ -801,6 +897,7 @@ static PyType_Slot DynamicScope_Type_slots[] = { { Py_mp_subscript, void_cast( DynamicScope_getitem ) }, /* mp_subscript */ { Py_mp_ass_subscript, void_cast( DynamicScope_setitem ) }, /* mp_ass_subscript */ { Py_sq_contains, void_cast( DynamicScope_contains ) }, /* sq_contains */ + { Py_tp_iter, void_cast( DynamicScope_iter ) }, /* tp_iter */ { 0, 0 }, }; diff --git a/tests/core/test_dynamicscope.py b/tests/core/test_dynamicscope.py index e2d282968..2249e0646 100644 --- a/tests/core/test_dynamicscope.py +++ b/tests/core/test_dynamicscope.py @@ -242,6 +242,31 @@ def test_dynamicscope_del(dynamicscope): assert 'str' in excinfo.exconly() +def test_dynamicscope_mapping(dynamicscope): + """Test the contains items, keys, value, update, and iter. + + """ + dynamicscope, extra = dynamicscope + owner = extra[0] + change = extra[4] + + assert set(dynamicscope.keys()) == {'a', 'b', 'c', 'e', 'self', 'change'} + + for v in dynamicscope.values(): + assert v in [1, 2, 3, 4, 5, owner, change] + + dynamicscope.update({"x": "y"}) + + with pytest.raises(TypeError): + dynamicscope.update(1) # not mapping + with pytest.raises(TypeError): + dynamicscope.update({1: 2}) # invalid key type + + assert {k for k in dynamicscope} == {'a', 'b', 'c', 'e', 'self', 'change', 'x'} + + assert dict(dynamicscope.items())["x"] == "y" + + @pytest.fixture def nonlocals(dynamicscope): """Access the nonlocals of a dynamic scope.