Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-106016: Support customizing of module attributes access with __setattr__/__delattr__ (PEP 726)#108261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
gh-106016: Support customizing of module attributes access with __setattr__/__delattr__ (PEP 726) #108261
Changes from all commits
a8404653e782250a8cec62904f50bce1667bcafd0d0740efb74c3d1383c4c507fe6a0647d93cef3f796df971a2268eefbd048d8bf058412e49c82c5280fc0edafc5469dcacafcd0e4beFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -151,6 +151,57 @@ def test_module_getattr_errors(self): | ||
| if 'test.test_module.bad_getattr2' in sys.modules: | ||
| del sys.modules['test.test_module.bad_getattr2'] | ||
| def test_module_setattr(self): | ||
| import test.test_module.good_setattr as gsa | ||
| self.assertEqual(gsa.foo, 1) | ||
| with self.assertRaises(AttributeError): | ||
| gsa.bar | ||
| with self.assertRaisesRegex(AttributeError, "Read-only attribute"): | ||
| gsa.foo = 2 | ||
| gsa.bar = 3 | ||
| self.assertEqual(gsa.bar, 3) | ||
skirpichev marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| del sys.modules['test.test_module.good_setattr'] | ||
| def test_module_setattr_errors(self): | ||
| import test.test_module.bad_setattr as bsa | ||
| from test.test_module import bad_setattr2 | ||
| self.assertEqual(bsa.foo, 1) | ||
| self.assertEqual(bad_setattr2.foo, 1) | ||
| with self.assertRaises(TypeError): | ||
| bsa.foo = 2 | ||
| with self.assertRaises(TypeError): | ||
| bad_setattr2.foo = 2 | ||
| del sys.modules['test.test_module.bad_setattr'] | ||
| if 'test.test_module.bad_setattr2' in sys.modules: | ||
| del sys.modules['test.test_module.bad_setattr2'] | ||
| def test_module_delattr(self): | ||
| import test.test_module.good_delattr as gda | ||
| self.assertEqual(gda.foo, 1) | ||
| with self.assertRaises(AttributeError): | ||
| gda.bar | ||
| with self.assertRaisesRegex(AttributeError, "Read-only attribute"): | ||
| del gda.foo | ||
| gda.bar = 3 | ||
| self.assertEqual(gda.bar, 3) | ||
| del gda.bar | ||
| with self.assertRaises(AttributeError): | ||
| gda.bar | ||
skirpichev marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| del sys.modules['test.test_module.good_delattr'] | ||
| def test_module_delattr_errors(self): | ||
| import test.test_module.bad_delattr as bda | ||
| from test.test_module import bad_delattr2 | ||
| self.assertEqual(bda.foo, 1) | ||
| self.assertEqual(bad_delattr2.foo, 1) | ||
| with self.assertRaises(TypeError): | ||
| del bda.foo | ||
| with self.assertRaises(TypeError): | ||
| del bad_delattr2.foo | ||
| del sys.modules['test.test_module.bad_delattr'] | ||
| if 'test.test_module.bad_delattr2' in sys.modules: | ||
| del sys.modules['test.test_module.bad_delattr2'] | ||
| def test_module_dir(self): | ||
| import test.test_module.good_getattr as gga | ||
| self.assertEqual(dir(gga), ['a', 'b', 'c']) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| foo = 1 | ||
| __delattr__ = "Oops" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| foo = 1 | ||
| def __delattr__(): | ||
| "Bad function signature" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| foo = 1 | ||
| __setattr__ = "Oops" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| foo = 1 | ||
| def __setattr__(): | ||
| "Bad function signature" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| foo = 1 | ||
| def __delattr__(name): | ||
| if name == 'foo': | ||
| raise AttributeError("Read-only attribute") | ||
| del globals()[name] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| foo = 1 | ||
| def __setattr__(name, value): | ||
| if name == 'foo': | ||
| raise AttributeError("Read-only attribute") | ||
| globals()[name] = value |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Add support for module ``__setattr__`` and ``__delattr__``. Patch by Sergey | ||
| B Kirpichev. | ||
skirpichev marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -891,6 +891,43 @@ _Py_module_getattro(PyModuleObject *m, PyObject *name) | ||
| return _Py_module_getattro_impl(m, name, 0); | ||
| } | ||
| static int | ||
| module_setattro(PyModuleObject *mod, PyObject *name, PyObject *value) | ||
| { | ||
| assert(mod->md_dict != NULL); | ||
| if (value){ | ||
| PyObject *setattr; | ||
| if (PyDict_GetItemRef(mod->md_dict, &_Py_ID(__setattr__), &setattr) < 0){ | ||
| return -1; | ||
| } | ||
| if (setattr){ | ||
| PyObject *res = PyObject_CallFunctionObjArgs(setattr, name, value, NULL); | ||
| Py_DECREF(setattr); | ||
| if (res == NULL){ | ||
| return -1; | ||
| } | ||
skirpichev marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| Py_DECREF(res); | ||
| return 0; | ||
| } | ||
| } | ||
| else{ | ||
| PyObject *delattr; | ||
| if (PyDict_GetItemRef(mod->md_dict, &_Py_ID(__delattr__), &delattr) < 0){ | ||
| return -1; | ||
| } | ||
| if (delattr){ | ||
| PyObject *res = PyObject_CallFunctionObjArgs(delattr, name, NULL); | ||
| Py_DECREF(delattr); | ||
| if (res == NULL){ | ||
| return -1; | ||
| } | ||
| Py_DECREF(res); | ||
| return 0; | ||
| } | ||
| } | ||
| return PyObject_GenericSetAttr((PyObject *)mod, name, value); | ||
| } | ||
| static int | ||
| module_traverse(PyModuleObject *m, visitproc visit, void *arg) | ||
| { | ||
| @@ -1039,7 +1076,7 @@ PyTypeObject PyModule_Type ={ | ||
| 0, /* tp_call */ | ||
| 0, /* tp_str */ | ||
| (getattrofunc)_Py_module_getattro, /* tp_getattro */ | ||
| PyObject_GenericSetAttr, /* tp_setattro */ | ||
| (setattrofunc)module_setattro, /* tp_setattro */ | ||
| 0, /* tp_as_buffer */ | ||
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | | ||
| Py_TPFLAGS_BASETYPE, /* tp_flags */ | ||
Uh oh!
There was an error while loading. Please reload this page.