Skip to content

Commit b8d4a71

Browse files
Add data() method to exiv2.DataValue
This copies the data into a new bytearray object, which is what a Python script had to do to get at the raw data. After the next release of exiv2 it will give direct access to the data.
1 parent 5bcfe72 commit b8d4a71

File tree

5 files changed

+164
-18
lines changed

5 files changed

+164
-18
lines changed

CHANGELOG.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ Changes in v0.18.0:
3636
aliases without the underscore.
3737
8/ Add data() method to exiv2.PreviewImage, deprecate pData() method.
3838
9/ Add data() method to exiv2.Image. This will replace using Image.io().
39-
10/ BasicIo.read (& readOrThrow) now extract count from the buffer size.
40-
11/ Invalidate data iterators if data is deleted. (Requires swig >= 4.4)
41-
12/ Deprecated iteration of exiv2 "data" structure types.
42-
13/ API CHANGE: exiv2.LogMsg.pythonHandler is replaced by exiv2.pythonHandler
39+
10/ Add data() method to exiv2.DataValue.
40+
11/ BasicIo.read (& readOrThrow) now extract count from the buffer size.
41+
12/ Invalidate data iterators if data is deleted. (Requires swig >= 4.4)
42+
13/ Deprecated iteration of exiv2 "data" structure types.
43+
14/ API CHANGE: exiv2.LogMsg.pythonHandler is replaced by exiv2.pythonHandler
4344

4445
Changes in v0.17.5:
4546
1/ Binary wheels incorporate libexiv2 v0.28.7.

src/interface/value.i

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,16 +430,13 @@ VALUE_SUBCLASS(Exiv2::LangAltValue, LangAltValue)
430430
VALUE_SUBCLASS(Exiv2::XmpArrayValue, XmpArrayValue)
431431
VALUE_SUBCLASS(Exiv2::XmpTextValue, XmpTextValue)
432432

433-
// Undeprecate DataValue::copy - it's the only way to get the data
434-
EXCEPTION(Exiv2::DataValue::copy)
435-
436433
// Allow access to Exiv2::StringValueBase and Exiv2::XmpTextValue raw data
437434
%define RAW_STRING_DATA(class)
438435
RETURN_VIEW(const char* data, arg1->value_.size(), PyBUF_READ, class##::data)
439436
%noexception class::data;
440437
%extend class {
441438
const char* data() {
442-
return $self->value_.data();
439+
return (char*)self->value_.data();
443440
};
444441
}
445442
DEFINE_VIEW_CALLBACK(class,)
@@ -451,6 +448,36 @@ DEFINE_VIEW_CALLBACK(class,)
451448
RAW_STRING_DATA(Exiv2::StringValueBase)
452449
RAW_STRING_DATA(Exiv2::XmpTextValue)
453450

451+
// Add data() method to DataValue
452+
#if EXIV2_VERSION_HEX >= 0x001c0800
453+
RAW_STRING_DATA(Exiv2::DataValue)
454+
#else
455+
%feature("docstring") Exiv2::DataValue::data "Return a copy of the raw data.
456+
457+
Allocates a :obj:`bytearray` of the correct size and copies the value's
458+
data into it.
459+
460+
:rtype: bytearray"
461+
%extend Exiv2::DataValue {
462+
PyObject* data() {
463+
PyObject* result = PyByteArray_FromStringAndSize(NULL, 0);
464+
if (!result)
465+
return NULL;
466+
if (PyByteArray_Resize(result, self->size()))
467+
return NULL;
468+
PyObject* view = PyMemoryView_FromObject(result);
469+
if (!view) {
470+
Py_DECREF(result);
471+
return NULL;
472+
}
473+
Py_buffer* buffer = PyMemoryView_GET_BUFFER(view);
474+
self->copy((Exiv2::byte*)buffer->buf);
475+
Py_DECREF(view);
476+
return result;
477+
}
478+
}
479+
#endif
480+
454481
// XmpArrayValue holds multiple values but they're not assignable
455482
%feature("python:slot", "sq_length", functype="lenfunc")
456483
Exiv2::XmpArrayValue::count;

src/swig-0_27_7/value_wrap.cxx

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6158,6 +6158,22 @@ SWIGINTERNINLINE PyObject*
61586158
return PyBool_FromLong(value ? 1 : 0);
61596159
}
61606160

6161+
SWIGINTERN PyObject *Exiv2_DataValue_data(Exiv2::DataValue *self){
6162+
PyObject* result = PyByteArray_FromStringAndSize(NULL, 0);
6163+
if (!result)
6164+
return NULL;
6165+
if (PyByteArray_Resize(result, self->size()))
6166+
return NULL;
6167+
PyObject* view = PyMemoryView_FromObject(result);
6168+
if (!view) {
6169+
Py_DECREF(result);
6170+
return NULL;
6171+
}
6172+
Py_buffer* buffer = PyMemoryView_GET_BUFFER(view);
6173+
self->copy((Exiv2::byte*)buffer->buf);
6174+
Py_DECREF(view);
6175+
return result;
6176+
}
61616177

61626178
static PyObject* _get_store(PyObject* py_self, bool create) {
61636179
// Return a new reference
@@ -6243,7 +6259,7 @@ static int release_views(PyObject* py_self) {
62436259
};
62446260

62456261
SWIGINTERN char const *Exiv2_StringValueBase_data(Exiv2::StringValueBase *self){
6246-
return self->value_.data();
6262+
return (char*)self->value_.data();
62476263
}
62486264
SWIGINTERN void Exiv2_StringValueBase__view_deleted_cb(Exiv2::StringValueBase *self,PyObject *ref){}
62496265
SWIGINTERN Exiv2::AsciiValue *new_Exiv2_AsciiValue__SWIG_1(std::string const &buf){
@@ -6255,7 +6271,7 @@ SWIGINTERN Exiv2::AsciiValue *new_Exiv2_AsciiValue__SWIG_1(std::string const &bu
62556271
return self;
62566272
}
62576273
SWIGINTERN char const *Exiv2_XmpTextValue_data(Exiv2::XmpTextValue *self){
6258-
return self->value_.data();
6274+
return (char*)self->value_.data();
62596275
}
62606276
SWIGINTERN void Exiv2_XmpTextValue__view_deleted_cb(Exiv2::XmpTextValue *self,PyObject *ref){}
62616277

@@ -8981,6 +8997,7 @@ SWIGINTERN PyObject *_wrap_DataValue_copy(PyObject *self, PyObject *args) {
89818997
}
89828998
}
89838999
{
9000+
PyErr_WarnEx(PyExc_DeprecationWarning, "Python scripts should not need to call ""Exiv2::DataValue::copy", 1);
89849001
try {
89859002
result = (long)((Exiv2::DataValue const *)arg1)->copy(arg2,arg3);
89869003
}
@@ -9290,6 +9307,35 @@ SWIGINTERN PyObject *_wrap_DataValue_toRational(PyObject *self, PyObject *args)
92909307
}
92919308

92929309

9310+
SWIGINTERN PyObject *_wrap_DataValue_data(PyObject *self, PyObject *args) {
9311+
PyObject *resultobj = 0;
9312+
Exiv2::DataValue *arg1 = (Exiv2::DataValue *) 0 ;
9313+
void *argp1 = 0 ;
9314+
int res1 = 0 ;
9315+
PyObject *result = 0 ;
9316+
9317+
if (args && PyTuple_Check(args) && PyTuple_GET_SIZE(args) > 0) SWIG_exception_fail(SWIG_TypeError, "DataValue_data takes no arguments");
9318+
res1 = SWIG_ConvertPtr(self, &argp1,SWIGTYPE_p_Exiv2__DataValue, 0 | 0 );
9319+
if (!SWIG_IsOK(res1)) {
9320+
SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "DataValue_data" "', argument " "1"" of type '" "Exiv2::DataValue *""'");
9321+
}
9322+
arg1 = reinterpret_cast< Exiv2::DataValue * >(argp1);
9323+
{
9324+
try {
9325+
result = (PyObject *)Exiv2_DataValue_data(arg1);
9326+
}
9327+
catch(std::exception const& e) {
9328+
_set_python_exception();
9329+
SWIG_fail;
9330+
}
9331+
}
9332+
resultobj = result;
9333+
return resultobj;
9334+
fail:
9335+
return NULL;
9336+
}
9337+
9338+
92939339
SWIGPY_DESTRUCTOR_CLOSURE(_wrap_delete_DataValue) /* defines _wrap_delete_DataValue_destructor_closure */
92949340

92959341
SWIGPY_LENFUNC_CLOSURE(_wrap_DataValue_count) /* defines _wrap_DataValue_count_lenfunc_closure */
@@ -25811,6 +25857,14 @@ SWIGINTERN PyMethodDef SwigPyBuiltin__Exiv2__DataValue_methods[] = {
2581125857
{ "toLong", _wrap_DataValue_toLong, METH_VARARGS, "" },
2581225858
{ "toFloat", _wrap_DataValue_toFloat, METH_VARARGS, "" },
2581325859
{ "toRational", _wrap_DataValue_toRational, METH_VARARGS, "" },
25860+
{ "data", _wrap_DataValue_data, METH_VARARGS, "\n"
25861+
"Return a copy of the raw data.\n"
25862+
"\n"
25863+
"Allocates a :obj:`bytearray` of the correct size and copies the value's\n"
25864+
"data into it.\n"
25865+
"\n"
25866+
":rtype: bytearray\n"
25867+
"" },
2581425868
{ NULL, NULL, 0, NULL } /* Sentinel */
2581525869
};
2581625870

src/swig-0_28_7/value_wrap.cxx

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6328,6 +6328,22 @@ SWIGINTERNINLINE PyObject*
63286328
return PyBool_FromLong(value ? 1 : 0);
63296329
}
63306330

6331+
SWIGINTERN PyObject *Exiv2_DataValue_data(Exiv2::DataValue *self){
6332+
PyObject* result = PyByteArray_FromStringAndSize(NULL, 0);
6333+
if (!result)
6334+
return NULL;
6335+
if (PyByteArray_Resize(result, self->size()))
6336+
return NULL;
6337+
PyObject* view = PyMemoryView_FromObject(result);
6338+
if (!view) {
6339+
Py_DECREF(result);
6340+
return NULL;
6341+
}
6342+
Py_buffer* buffer = PyMemoryView_GET_BUFFER(view);
6343+
self->copy((Exiv2::byte*)buffer->buf);
6344+
Py_DECREF(view);
6345+
return result;
6346+
}
63316347

63326348
static PyObject* _get_store(PyObject* py_self, bool create) {
63336349
// Return a new reference
@@ -6413,7 +6429,7 @@ static int release_views(PyObject* py_self) {
64136429
};
64146430

64156431
SWIGINTERN char const *Exiv2_StringValueBase_data(Exiv2::StringValueBase *self){
6416-
return self->value_.data();
6432+
return (char*)self->value_.data();
64176433
}
64186434
SWIGINTERN void Exiv2_StringValueBase__view_deleted_cb(Exiv2::StringValueBase *self,PyObject *ref){}
64196435
SWIGINTERN Exiv2::AsciiValue *new_Exiv2_AsciiValue__SWIG_1(std::string const &buf){
@@ -6425,7 +6441,7 @@ SWIGINTERN Exiv2::AsciiValue *new_Exiv2_AsciiValue__SWIG_1(std::string const &bu
64256441
return self;
64266442
}
64276443
SWIGINTERN char const *Exiv2_XmpTextValue_data(Exiv2::XmpTextValue *self){
6428-
return self->value_.data();
6444+
return (char*)self->value_.data();
64296445
}
64306446
SWIGINTERN void Exiv2_XmpTextValue__view_deleted_cb(Exiv2::XmpTextValue *self,PyObject *ref){}
64316447

@@ -9016,6 +9032,7 @@ SWIGINTERN PyObject *_wrap_DataValue_copy(PyObject *self, PyObject *args) {
90169032
}
90179033
}
90189034
{
9035+
PyErr_WarnEx(PyExc_DeprecationWarning, "Python scripts should not need to call ""Exiv2::DataValue::copy", 1);
90199036
try {
90209037
result = ((Exiv2::DataValue const *)arg1)->copy(arg2,arg3);
90219038
}
@@ -9344,6 +9361,35 @@ SWIGINTERN PyObject *_wrap_DataValue_toRational(PyObject *self, PyObject *args)
93449361
}
93459362

93469363

9364+
SWIGINTERN PyObject *_wrap_DataValue_data(PyObject *self, PyObject *args) {
9365+
PyObject *resultobj = 0;
9366+
Exiv2::DataValue *arg1 = (Exiv2::DataValue *) 0 ;
9367+
void *argp1 = 0 ;
9368+
int res1 = 0 ;
9369+
PyObject *result = 0 ;
9370+
9371+
if (args && PyTuple_Check(args) && PyTuple_GET_SIZE(args) > 0) SWIG_exception_fail(SWIG_TypeError, "DataValue_data takes no arguments");
9372+
res1 = SWIG_ConvertPtr(self, &argp1,SWIGTYPE_p_Exiv2__DataValue, 0 | 0 );
9373+
if (!SWIG_IsOK(res1)) {
9374+
SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "DataValue_data" "', argument " "1"" of type '" "Exiv2::DataValue *""'");
9375+
}
9376+
arg1 = reinterpret_cast< Exiv2::DataValue * >(argp1);
9377+
{
9378+
try {
9379+
result = (PyObject *)Exiv2_DataValue_data(arg1);
9380+
}
9381+
catch(std::exception const& e) {
9382+
_set_python_exception();
9383+
SWIG_fail;
9384+
}
9385+
}
9386+
resultobj = result;
9387+
return resultobj;
9388+
fail:
9389+
return NULL;
9390+
}
9391+
9392+
93479393
SWIGINTERN PyObject *_wrap_delete_DataValue(PyObject *self, PyObject *args) {
93489394
PyObject *resultobj = 0;
93499395
Exiv2::DataValue *arg1 = (Exiv2::DataValue *) 0 ;
@@ -25935,6 +25981,14 @@ SWIGINTERN PyMethodDef SwigPyBuiltin__Exiv2__DataValue_methods[] = {
2593525981
{ "toUint32", _wrap_DataValue_toUint32, METH_VARARGS, "" },
2593625982
{ "toFloat", _wrap_DataValue_toFloat, METH_VARARGS, "" },
2593725983
{ "toRational", _wrap_DataValue_toRational, METH_VARARGS, "" },
25984+
{ "data", _wrap_DataValue_data, METH_VARARGS, "\n"
25985+
"Return a copy of the raw data.\n"
25986+
"\n"
25987+
"Allocates a :obj:`bytearray` of the correct size and copies the value's\n"
25988+
"data into it.\n"
25989+
"\n"
25990+
":rtype: bytearray\n"
25991+
"" },
2593825992
{ NULL, NULL, 0, NULL } /* Sentinel */
2593925993
};
2594025994

tests/test_value.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,9 @@ def do_common_tests(self, value, type_id, string, data, sequence=None):
4242
self.assertIsInstance(result, type(value))
4343
self.assertEqual(str(result), str(value))
4444
result = bytearray(len(data))
45-
if type_id in (exiv2.TypeId.undefined, exiv2.TypeId.unsignedByte):
45+
with self.assertWarns(DeprecationWarning):
4646
self.assertEqual(value.copy(
4747
result, exiv2.ByteOrder.littleEndian), len(result))
48-
else:
49-
with self.assertWarns(DeprecationWarning):
50-
self.assertEqual(value.copy(
51-
result, exiv2.ByteOrder.littleEndian), len(result))
5248
self.assertEqual(result, data)
5349
if sequence:
5450
self.check_result(value.count(), int, len(sequence))
@@ -326,7 +322,8 @@ def test_XmpTextValue(self):
326322
def test_DataValue(self):
327323
def check_data(value, data):
328324
copy = bytearray(len(data))
329-
self.assertEqual(value.copy(copy), len(data))
325+
with self.assertWarns(DeprecationWarning):
326+
self.assertEqual(value.copy(copy), len(data))
330327
self.assertEqual(copy, data)
331328

332329
data = bytes(random.choices(range(256), k=128))
@@ -352,6 +349,19 @@ def check_data(value, data):
352349
value.typeId(), exiv2.TypeId, exiv2.TypeId.unsignedByte)
353350
check_data(value, data)
354351
# other methods
352+
if exiv2.testVersion(0, 28, 8):
353+
with value.data() as view:
354+
self.assertIsInstance(view, memoryview)
355+
self.assertEqual(view, data)
356+
with self.assertRaises(ValueError):
357+
self.assertEqual(view[0], data[0])
358+
self.assertEqual(sys.getrefcount(value), 3)
359+
del view
360+
self.assertEqual(sys.getrefcount(value), 2)
361+
else:
362+
copy = value.data()
363+
self.assertIsInstance(copy, bytearray)
364+
self.assertEqual(copy, data)
355365
self.do_common_tests(value, exiv2.TypeId.unsignedByte, string, data)
356366
self.do_conversion_tests(value, str(data[0]), data[0])
357367
self.do_dataarea_tests(value)

0 commit comments

Comments
 (0)