{%- block pxd_header %} {% endblock -%} {%- block pyx_header %} {% endblock -%} {# We can't do const in Python #} {{ force_mark_rendered("pandemonium_dictionary_operator_index_const") }} @cython.final cdef class Dictionary: {% block cdef_attributes %} cdef pandemonium_dictionary _gd_data @staticmethod cdef inline Dictionary new() @staticmethod cdef inline Dictionary from_ptr(const pandemonium_dictionary *_ptr) cdef inline operator_update(self, Dictionary items) cdef inline bint operator_equal(self, Dictionary other) {% endblock %} {% block python_defs %} def __init__(self, iterable=None): {{ force_mark_rendered("pandemonium_dictionary_new") }} if not iterable: gdapi10.pandemonium_dictionary_new(&self._gd_data) elif isinstance(iterable, Dictionary): self._gd_data = gdapi12.pandemonium_dictionary_duplicate(&(iterable)._gd_data, False) # TODO: handle Pool*Array elif isinstance(iterable, dict): gdapi10.pandemonium_dictionary_new(&self._gd_data) for k, v in iterable.items(): self[k] = v else: gdapi10.pandemonium_dictionary_new(&self._gd_data) try: for k, v in iterable: self[k] = v except ValueError as exc: raise ValueError("dictionary update sequence element has length 1; 2 is required") def __dealloc__(self): {{ force_mark_rendered("pandemonium_dictionary_destroy") }} # /!\ if `__init__` is skipped, `_gd_data` must be initialized by # hand otherwise we will get a segfault here gdapi10.pandemonium_dictionary_destroy(&self._gd_data) @staticmethod cdef inline Dictionary new(): # Call to __new__ bypasses __init__ constructor cdef Dictionary ret = Dictionary.__new__(Dictionary) gdapi10.pandemonium_dictionary_new(&ret._gd_data) return ret @staticmethod cdef inline Dictionary from_ptr(const pandemonium_dictionary *_ptr): # Call to __new__ bypasses __init__ constructor cdef Dictionary ret = Dictionary.__new__(Dictionary) # `pandemonium_dictionary` is a cheap structure pointing on a refcounted hashmap # of variants. Unlike it name could let think, `pandemonium_dictionary_new_copy` # only increment the refcount of the underlying structure. {{ force_mark_rendered("pandemonium_dictionary_new_copy") }} gdapi10.pandemonium_dictionary_new_copy(&ret._gd_data, _ptr) return ret def __repr__(self): repr_dict = {} for k, v in self.items(): if isinstance(k, GDString): k = str(k) if isinstance(v, GDString): v = str(v) repr_dict[k] = v return f"" def __getitem__(self, object key): {{ force_mark_rendered("pandemonium_dictionary_operator_index") }} cdef pandemonium_variant var_key if not pyobj_to_pandemonium_variant(key, &var_key): raise TypeError(f"Cannot convert `{key!r}` to Pandemonium Variant") cdef pandemonium_variant *p_var_ret = gdapi10.pandemonium_dictionary_operator_index(&self._gd_data, &var_key) gdapi10.pandemonium_variant_destroy(&var_key) if p_var_ret == NULL: raise KeyError(key) else: return pandemonium_variant_to_pyobj(p_var_ret) {{ render_method("set", py_name="__setitem__") | indent }} def __delitem__(self, object key): {{ force_mark_rendered("pandemonium_dictionary_erase_with_return") }} cdef pandemonium_variant var_key if not pyobj_to_pandemonium_variant(key, &var_key): raise TypeError(f"Cannot convert `{key!r}` to Pandemonium Variant") cdef pandemonium_bool ret = gdapi11.pandemonium_dictionary_erase_with_return(&self._gd_data, &var_key) gdapi10.pandemonium_variant_destroy(&var_key) if not ret: raise KeyError(key) def __iter__(self): {{ force_mark_rendered("pandemonium_dictionary_next") }} cdef pandemonium_variant *p_key = NULL # TODO: mid iteration mutation should throw exception ? while True: p_key = gdapi10.pandemonium_dictionary_next(&self._gd_data, p_key) if p_key == NULL: return yield pandemonium_variant_to_pyobj(p_key) def __copy__(self): return self.duplicate(False) def __deepcopy__(self): return self.duplicate(True) def get(self, object key, object default=None): {{ force_mark_rendered("pandemonium_dictionary_get") }} {{ force_mark_rendered("pandemonium_dictionary_get_with_default") }} cdef pandemonium_variant var_key pyobj_to_pandemonium_variant(key, &var_key) cdef pandemonium_variant var_ret cdef pandemonium_variant var_default if default is not None: pyobj_to_pandemonium_variant(default, &var_default) var_ret = gdapi11.pandemonium_dictionary_get_with_default(&self._gd_data, &var_key, &var_default) gdapi10.pandemonium_variant_destroy(&var_default) else: var_ret = gdapi10.pandemonium_dictionary_get(&self._gd_data, &var_key) gdapi10.pandemonium_variant_destroy(&var_key) cdef object ret = pandemonium_variant_to_pyobj(&var_ret) gdapi10.pandemonium_variant_destroy(&var_ret) return ret cdef inline operator_update(self, Dictionary items): cdef pandemonium_variant *p_value cdef pandemonium_variant *p_key = NULL while True: p_key = gdapi10.pandemonium_dictionary_next(&items._gd_data, p_key) if p_key == NULL: break p_value = gdapi10.pandemonium_dictionary_operator_index(&items._gd_data, p_key) gdapi10.pandemonium_dictionary_set(&self._gd_data, p_key, p_value) return self def update(self, other): cdef object k cdef object v if isinstance(other, Dictionary): Dictionary.operator_update(self, other) elif isinstance(other, dict): for k, v in other.items(): self[k] = v else: raise TypeError("other must be pandemonium.Dictionary or dict") def items(self): cdef pandemonium_variant *p_key = NULL cdef pandemonium_variant *p_value # TODO: mid iteration mutation should throw exception ? while True: p_key = gdapi10.pandemonium_dictionary_next(&self._gd_data, p_key) if p_key == NULL: return p_value = gdapi10.pandemonium_dictionary_operator_index(&self._gd_data, p_key) yield pandemonium_variant_to_pyobj(p_key), pandemonium_variant_to_pyobj(p_value) cdef inline bint operator_equal(self, Dictionary other): if other is None: return False cdef pandemonium_int size = self.size() if size != other.size(): return False # TODO: gdnative should provide a function to do that return dict(self) == dict(other) def __eq__(self, other): {# see https://github.com/pandemoniumengine/pandemonium/issues/27615 #} {{ force_mark_rendered("pandemonium_dictionary_operator_equal") }} try: return Dictionary.operator_equal(self, other) except TypeError: return False def __ne__(self, other): try: return not Dictionary.operator_equal(self, other) except TypeError: return True {{ render_method("size", py_name="__len__") | indent }} {{ render_method("hash", py_name="__hash__") | indent }} {{ render_method("has", py_name="__contains__") | indent }} {{ render_method("duplicate") | indent }} {{ render_method("size") | indent }} {{ render_method("empty") | indent }} {{ render_method("clear") | indent }} {{ render_method("has") | indent }} {{ render_method("has_all") | indent }} {{ render_method("erase") | indent }} {{ render_method("hash") | indent }} {{ render_method("keys") | indent }} {{ render_method("values") | indent }} {{ render_method("to_json") | indent }} {% endblock %} {%- block python_consts %} {% endblock %}