1use super::{PyStr, PyTupleRef, PyType, tuple::IntoPyTuple};
2use crate::{
3 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
4 builtins::PyDict,
5 class::PyClassImpl,
6 function::{KwArgs, NameChanges, OptionalArg, PyComparisonValue},
7 recursion::ReprGuard,
8 types::{
9 Comparable, Constructor, DefaultConstructor, Initializer, PyComparisonOp, Representable,
10 },
11};
12use rustpython_common::wtf8::Wtf8Buf;
13
14#[pyclass(module = "types", name = "SimpleNamespace", unhashable = true)]
15#[pymember(name = "__dict__", offset = ::rustpython_vm::object::dict_member_offset())]
18#[derive(Debug, Default)]
19pub struct PyNamespace {}
20
21impl PyPayload for PyNamespace {
22 #[inline]
23 fn class(ctx: &Context) -> &'static Py<PyType> {
24 ctx.types.namespace_type
25 }
26}
27
28impl DefaultConstructor for PyNamespace {}
29
30#[pyclass(
31 flags(BASETYPE, HAS_DICT, HAS_WEAKREF),
32 with(Constructor, Initializer, Comparable, Representable)
33)]
34impl PyNamespace {
35 #[pymethod]
36 fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyTupleRef {
37 let dict = zelf.as_object().dict().unwrap();
38 let obj = zelf.as_object().to_owned();
39 let result: (PyObjectRef, PyObjectRef, PyObjectRef) = (
40 obj.class().to_owned().into(),
41 vm.new_tuple(()).into(),
42 dict.into(),
43 );
44 result.into_pytuple(vm)
45 }
46
47 #[pymethod]
48 fn __replace__(
49 zelf: PyObjectRef,
50 changes: KwArgs<PyObjectRef, NameChanges>,
51 vm: &VirtualMachine,
52 ) -> PyResult {
53 let cls: PyObjectRef = zelf.class().to_owned().into();
55 let result = cls.call((), vm)?;
56
57 if !zelf.class().is(result.class()) {
58 return Err(vm.new_type_error(format!(
59 "expect {} type, but {}() returned '{}' object",
60 Self::class(&vm.ctx).slot_name(),
61 zelf.class()
62 .__qualname__(vm)
63 .downcast_ref::<PyStr>()
64 .map_or_else(
65 || zelf.class().name().to_string(),
66 |n| n.as_wtf8().to_string()
67 ),
68 result.class().name(),
69 )));
70 }
71
72 let src_dict = zelf.dict().unwrap();
74 let dst_dict = result.dict().unwrap();
75 for (key, value) in src_dict {
76 dst_dict.set_item(&*key, value, vm)?;
77 }
78
79 for (name, value) in changes {
81 let name = vm.ctx.new_str(name);
82 result.set_attr(&name, value, vm)?;
83 }
84
85 Ok(result)
86 }
87}
88
89#[derive(FromArgs)]
90pub struct NamespaceArgs {
91 #[pyarg(positional, default, py_default = "()")]
92 mapping_or_iterable: OptionalArg<PyObjectRef>,
93 #[pyarg(flatten)]
94 kwargs: KwArgs,
95}
96
97impl Initializer for PyNamespace {
98 type Args = NamespaceArgs;
99
100 fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
101 if let OptionalArg::Present(mapping) = args.mapping_or_iterable {
103 let dict: PyRef<PyDict> = if let Some(d) = mapping.downcast_ref::<PyDict>() {
105 d.to_owned()
106 } else {
107 let dict_type: PyObjectRef = vm.ctx.types.dict_type.to_owned().into();
109 dict_type
110 .call((mapping,), vm)?
111 .downcast()
112 .map_err(|_| vm.new_type_error("dict() did not return a dict"))?
113 };
114
115 for (key, value) in dict {
117 let key_str = key
118 .downcast_ref::<crate::builtins::PyStr>()
119 .ok_or_else(|| {
120 vm.new_type_error(format!(
121 "keywords must be strings, not '{}'",
122 key.class().name()
123 ))
124 })?;
125 zelf.as_object().set_attr(key_str, value, vm)?;
126 }
127 }
128
129 for (name, value) in args.kwargs {
131 let name = vm.ctx.new_str(name);
132 zelf.as_object().set_attr(&name, value, vm)?;
133 }
134 Ok(())
135 }
136}
137
138impl Comparable for PyNamespace {
139 fn cmp(
140 zelf: &Py<Self>,
141 other: &PyObject,
142 op: PyComparisonOp,
143 vm: &VirtualMachine,
144 ) -> PyResult<PyComparisonValue> {
145 let other = class_or_notimplemented!(Self, other);
146 let (d1, d2) = (
147 zelf.as_object().dict().unwrap(),
148 other.as_object().dict().unwrap(),
149 );
150 PyDict::cmp(&d1, d2.as_object(), op, vm)
151 }
152}
153
154impl Representable for PyNamespace {
155 #[inline]
156 fn repr_wtf8(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
157 let o = zelf.as_object();
158 let name = if o.class().is(vm.ctx.types.namespace_type) {
159 "namespace".to_owned()
160 } else {
161 o.class().slot_name().to_owned()
162 };
163
164 let repr = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
165 let dict = zelf.as_object().dict().unwrap();
166 let mut result = Wtf8Buf::from(format!("{name}("));
167 let mut first = true;
168 for (key, value) in dict {
169 let Some(key_str) = key.downcast_ref::<PyStr>() else {
170 continue;
171 };
172 if key_str.as_wtf8().is_empty() {
173 continue;
174 }
175 if !first {
176 result.push_str(", ");
177 }
178 first = false;
179 result.push_wtf8(key_str.as_wtf8());
180 result.push_char('=');
181 result.push_wtf8(value.repr(vm)?.as_wtf8());
182 }
183 result.push_char(')');
184 result
185 } else {
186 Wtf8Buf::from(format!("{name}(...)"))
187 };
188 Ok(repr)
189 }
190}
191
192pub(crate) fn init(context: &'static Context) {
193 PyNamespace::extend_class(context, context.types.namespace_type);
194}