Skip to content

Commit 78e73cf

Browse files
Add aliases of struct member names ending in _
The trailing underscore is not very Pythonic, so the members can also be accessed without it.
1 parent f0e4c8b commit 78e73cf

13 files changed

Lines changed: 1494 additions & 1200 deletions

CHANGELOG.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Changes in v0.18.0:
2121
Value (and derived types) copy constructors
2222
Single value (such as DateValue) index methods
2323
2/ Added binary wheels for Linux on arm64.
24+
3/ Exiv2 struct member names with a trailing underscore have more Pythonic
25+
aliases without the underscore.
2426

2527
Changes in v0.17.3:
2628
1/ Binary wheels incorporate libexiv2 v0.28.5.

src/interface/shared/struct_dict.i

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ static PyObject* list_getset(
2626
PyObject* result = PyList_New(0);
2727
PyObject* item = NULL;
2828
while (getset->name) {
29+
// __dict__ is also in the getset list
2930
if (getset->name[0] != '_') {
3031
item = (*conv)(obj, getset);
3132
PyList_Append(result, item);
@@ -35,52 +36,82 @@ static PyObject* list_getset(
3536
}
3637
return result;
3738
};
39+
static PyGetSetDef* find_getset(PyObject* obj, PyObject* name,
40+
bool strip, bool required) {
41+
if (!PyUnicode_Check(name))
42+
return NULL;
43+
Py_ssize_t size = 0;
44+
const char* c_name = PyUnicode_AsUTF8AndSize(name, &size);
45+
bool truncate = strip && size > 0 && c_name[size - 1] != '_';
46+
PyGetSetDef* getset = Py_TYPE(obj)->tp_getset;
47+
size_t len = 0;
48+
while (getset->name) {
49+
len = strlen(getset->name);
50+
if (truncate && getset->name[len - 1] == '_')
51+
len--;
52+
if (len == (size_t) size && strncmp(getset->name, c_name, len) == 0)
53+
return getset;
54+
getset++;
55+
}
56+
if (required)
57+
PyErr_Format(PyExc_AttributeError,
58+
"'%s' object has no attribute '%U'",
59+
Py_TYPE(obj)->tp_name, name);
60+
return NULL;
61+
};
62+
static int getset_set(PyObject* obj, PyObject* name, PyObject* value,
63+
bool strip, bool required) {
64+
PyGetSetDef* getset = find_getset(obj, name, strip, required);
65+
if (getset) {
66+
#if SWIG_VERSION < 0x040400
67+
if (!value) {
68+
PyErr_Format(PyExc_TypeError,
69+
"%s.%s can not be deleted", Py_TYPE(obj)->tp_name, getset->name);
70+
return -1;
71+
}
72+
#endif // SWIG_VERSION
73+
return getset->set(obj, value, getset->closure);
74+
}
75+
if (required)
76+
return -1;
77+
return PyObject_GenericSetAttr(obj, name, value);
78+
};
3879
static PyObject* getset_to_value(PyObject* obj, PyGetSetDef* getset) {
3980
return Py_BuildValue("N", getset->get(obj, getset->closure));
4081
};
41-
}
42-
%fragment("getset_functions_strip", "header",
43-
fragment="getset_functions") {
4482
static PyObject* getset_to_item_strip(PyObject* obj, PyGetSetDef* getset) {
4583
return Py_BuildValue("(s#N)", getset->name, strlen(getset->name) - 1,
4684
getset->get(obj, getset->closure));
4785
};
48-
static PyObject* getset_to_key_strip(PyObject* obj, PyGetSetDef* getset) {
49-
return Py_BuildValue("s#", getset->name, strlen(getset->name) - 1);
50-
};
51-
}
52-
%fragment("getset_functions_nostrip", "header",
53-
fragment="getset_functions") {
5486
static PyObject* getset_to_item_nostrip(PyObject* obj, PyGetSetDef* getset) {
5587
return Py_BuildValue("(sN)", getset->name,
5688
getset->get(obj, getset->closure));
5789
};
90+
static PyObject* getset_to_key_strip(PyObject* obj, PyGetSetDef* getset) {
91+
return Py_BuildValue("s#", getset->name, strlen(getset->name) - 1);
92+
};
5893
static PyObject* getset_to_key_nostrip(PyObject* obj, PyGetSetDef* getset) {
5994
return Py_BuildValue("s", getset->name);
6095
};
61-
}
62-
%fragment("set_attr_no_delete", "header") {
63-
static int set_attr_no_delete(
64-
PyObject* obj, PyObject* name, PyObject* value) {
65-
if ((!value) && PyUnicode_Check(name)) {
66-
const char* c_name = PyUnicode_AsUTF8(name);
67-
PyGetSetDef* getset = Py_TYPE(obj)->tp_getset;
68-
while (getset->name) {
69-
if (strcmp(getset->name, c_name) == 0) {
70-
PyErr_Format(PyExc_TypeError,
71-
"%s.%s can not be deleted",
72-
Py_TYPE(obj)->tp_name, c_name);
73-
return -1;
74-
}
75-
getset++;
76-
}
77-
}
78-
return PyObject_GenericSetAttr(obj, name, value);
96+
static int set_attr_strip(PyObject* obj, PyObject* name, PyObject* value) {
97+
return getset_set(obj, name, value, true, false);
98+
};
99+
#if SWIG_VERSION < 0x040400
100+
static int set_attr_nostrip(PyObject* obj, PyObject* name, PyObject* value) {
101+
return getset_set(obj, name, value, false, false);
102+
};
103+
#endif // SWIG_VERSION
104+
static PyObject* get_attr_strip(PyObject* obj, PyObject* name) {
105+
PyGetSetDef* getset = find_getset(obj, name, true, false);
106+
if (getset)
107+
return getset_to_value(obj, getset);
108+
return PyObject_GenericGetAttr(obj, name);
79109
};
80110
}
81111

82112
// Macro definition
83113
%define STRUCT_DICT(struct_type, mutable, strip_underscore)
114+
%fragment("getset_functions");
84115
// Type slots
85116
%feature("python:slot", "tp_iter", functype="getiterfunc")
86117
struct_type::__iter__;
@@ -104,15 +135,13 @@ static int set_attr_no_delete(
104135
// Add functions
105136
%extend struct_type {
106137
#if #strip_underscore == "true"
107-
%fragment("getset_functions_strip");
108138
PyObject* items(PyObject* py_self) {
109139
return list_getset(py_self, getset_to_item_strip);
110140
}
111141
PyObject* keys(PyObject* py_self) {
112142
return list_getset(py_self, getset_to_key_strip);
113143
}
114144
#else
115-
%fragment("getset_functions_nostrip");
116145
PyObject* items(PyObject* py_self) {
117146
return list_getset(py_self, getset_to_item_nostrip);
118147
}
@@ -130,33 +159,31 @@ static int set_attr_no_delete(
130159
Py_DECREF(seq);
131160
return result;
132161
}
133-
PyObject* __getitem__(PyObject* py_self, const std::string& key) {
134-
#if #strip_underscore == "true"
135-
return PyObject_GetAttrString(py_self, (key + '_').c_str());
136-
#else
137-
return PyObject_GetAttrString(py_self, key.c_str());
138-
#endif // strip_underscore
162+
PyObject* __getitem__(PyObject* py_self, PyObject* key) {
163+
PyGetSetDef* getset = find_getset(
164+
py_self, key, strip_underscore, true);
165+
if (!getset)
166+
return NULL;
167+
return getset_to_value(py_self, getset);
139168
}
140169
}
170+
#if #strip_underscore == "true"
171+
%feature("python:tp_getattro") struct_type "get_attr_strip";
172+
#endif // strip_underscore
141173
#if #mutable == "true"
142-
%fragment("set_attr_no_delete");
143-
%feature("python:tp_setattro") struct_type "set_attr_no_delete";
174+
#if #strip_underscore == "true"
175+
%feature("python:tp_setattro") struct_type "set_attr_strip";
176+
#else // strip_underscore
177+
#if SWIG_VERSION < 0x040400
178+
%feature("python:tp_setattro") struct_type "set_attr_nostrip";
179+
#endif // SWIG_VERSION
180+
#endif // strip_underscore
144181
%feature("python:slot", "mp_ass_subscript", functype="objobjargproc")
145182
struct_type::__setitem__;
146183
%extend struct_type {
147-
PyObject* __setitem__(PyObject* py_self, const std::string& key,
184+
PyObject* __setitem__(PyObject* py_self, PyObject* key,
148185
PyObject* value) {
149-
if (!value)
150-
return PyErr_Format(PyExc_TypeError,
151-
"%s['%s'] can not be deleted", Py_TYPE(py_self)->tp_name,
152-
key.c_str());
153-
#if #strip_underscore == "true"
154-
int error = PyObject_SetAttrString(
155-
py_self, (key + '_').c_str(), value);
156-
#else
157-
int error = PyObject_SetAttrString(py_self, key.c_str(), value);
158-
#endif // strip_underscore
159-
if (error)
186+
if (getset_set(py_self, key, value, strip_underscore, true))
160187
return NULL;
161188
return SWIG_Py_Void();
162189
}

0 commit comments

Comments
 (0)