Skip to content

Commit 4ec2fd7

Browse files
authored
Merge pull request #86 from RhetTbull/bug_kMDItemKeywords_string_value_83
Bug k md item keywords string value 83
2 parents 243081f + 20ef4ad commit 4ec2fd7

File tree

9 files changed

+170
-11
lines changed

9 files changed

+170
-11
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.2.0
2+
current_version = 1.2.1
33
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
44
serialize = {major}.{minor}.{patch}
55

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,23 @@ Options:
298298
-j, --json Print output in JSON format, for use with
299299
--list and --get.
300300
-X, --wipe Wipe all metadata attributes from FILE.
301-
-s, --set ATTRIBUTE VALUE Set ATTRIBUTE to VALUE.
301+
-s, --set ATTRIBUTE VALUE Set ATTRIBUTE to VALUE. If ATTRIBUTE is a
302+
multi-value attribute, such as keywords
303+
(kMDItemKeywords), you may specify --set
304+
multiple times to add to the array of values:
305+
'--set keywords foo --set keywords bar' will
306+
set keywords to ['foo', 'bar']. Not that this
307+
will overwrite any existing values for the
308+
attribute; see also --append.
302309
-l, --list List all metadata attributes for FILE.
303310
-c, --clear ATTRIBUTE Remove attribute from FILE.
304311
-a, --append ATTRIBUTE VALUE Append VALUE to ATTRIBUTE; for multi-valued
305312
attributes, appends only if VALUE is not
306-
already present.
313+
already present. May be used in combination
314+
with --set to add to an existing value: '--set
315+
keywords foo --append keywords bar' will set
316+
keywords to ['foo', 'bar'], overwriting any
317+
existing values for the attribute.
307318
-g, --get ATTRIBUTE Get value of ATTRIBUTE.
308319
-r, --remove ATTRIBUTE VALUE Remove VALUE from ATTRIBUTE; only applies to
309320
multi-valued attributes.

osxmetadata/__main__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,11 @@ def get_help(self, ctx):
745745
"-s",
746746
"set_",
747747
metavar="ATTRIBUTE VALUE",
748-
help="Set ATTRIBUTE to VALUE.",
748+
help="Set ATTRIBUTE to VALUE. "
749+
"If ATTRIBUTE is a multi-value attribute, such as keywords (kMDItemKeywords), "
750+
"you may specify --set multiple times to add to the array of values: "
751+
"'--set keywords foo --set keywords bar' will set keywords to ['foo', 'bar']. "
752+
"Not that this will overwrite any existing values for the attribute; see also --append.",
749753
nargs=2,
750754
multiple=True,
751755
required=False,
@@ -788,7 +792,10 @@ def get_help(self, ctx):
788792
"--append",
789793
"-a",
790794
metavar="ATTRIBUTE VALUE",
791-
help="Append VALUE to ATTRIBUTE; for multi-valued attributes, appends only if VALUE is not already present.",
795+
help="Append VALUE to ATTRIBUTE; for multi-valued attributes, appends only if VALUE is not already present. "
796+
"May be used in combination with --set to add to an existing value: "
797+
"'--set keywords foo --append keywords bar' will set keywords to ['foo', 'bar'], "
798+
"overwriting any existing values for the attribute.",
792799
nargs=2,
793800
multiple=True,
794801
required=False,

osxmetadata/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
""" osxmetadata version """
22

3-
__version__ = "1.2.0"
3+
__version__ = "1.2.1"

osxmetadata/mditem.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
# appropriate Objective C object pointers.
5252

5353

54-
def MDItemSetAttribute(mditem, name, attr):
54+
def MDItemSetAttribute(mditem, name, attr) -> bool:
5555
"""dummy function definition"""
5656
...
5757

@@ -100,7 +100,7 @@ def set_mditem_metadata(
100100
) -> bool:
101101
"""Set file metadata using undocumented function MDItemSetAttribute
102102
103-
file: path to file
103+
mditem: MDItem object
104104
attribute: metadata attribute to set
105105
value: value to set attribute to; must match the type expected by the attribute (e.g. bool, str, List[str], float, datetime.datetime)
106106
@@ -149,7 +149,13 @@ def get_mditem_metadata(
149149
elif attribute_type == "float":
150150
return float(value)
151151
elif attribute_type == "list":
152-
return [str(x) for x in value]
152+
# some attributes like kMDItemKeywords do not always follow the documented type
153+
# and can return a single comma-delimited string instead of a list (See #83)
154+
return (
155+
str(value).split(",")
156+
if isinstance(value, (objc.pyobjc_unicode, str))
157+
else [str(x) for x in value]
158+
)
153159
elif attribute_type == "datetime.datetime":
154160
return CFDate_to_datetime(value)
155161
elif attribute_type == "list[datetime.datetime]":
@@ -160,6 +166,8 @@ def get_mditem_metadata(
160166
elif "__NSTaggedDate" in repr(type(value)):
161167
# this is a hack but works for MDImporter attributes that don't have a documented type
162168
return NSDate_to_datetime(value)
169+
elif isinstance(value, objc.pyobjc_unicode):
170+
return str(value)
163171
else:
164172
return value
165173
except ValueError:

osxmetadata/osxmetadata.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131
set_finderinfo_stationerypad,
3232
)
3333
from .finder_tags import _kMDItemUserTags, get_finder_tags, set_finder_tags
34-
from .mditem import MDItemValueType, get_mditem_metadata, set_or_remove_mditem_metadata
34+
from .mditem import (
35+
MDItemValueType,
36+
get_mditem_metadata,
37+
set_or_remove_mditem_metadata,
38+
MDItemSetAttribute,
39+
)
3540
from .nsurl_metadata import get_nsurl_metadata, set_nsurl_metadata
3641

3742
ALL_ATTRIBUTES = {
@@ -197,6 +202,34 @@ def to_json(
197202

198203
return json.dumps(dict_data, indent=indent)
199204

205+
def get_mditem_attribute_value(self, attribute: str) -> t.Any:
206+
"""Get the raw MDItem attribute value without any type conversion.
207+
208+
Args:
209+
attribute: metadata attribute name
210+
211+
Returns:
212+
raw MDItem attribute value as returned by CoreServices.MDItemCopyAttribute()
213+
214+
Note: This is a low level function that you probably don't need to use,
215+
but may be useful in some cases. You should probably use the get() method instead.
216+
"""
217+
return CoreServices.MDItemCopyAttribute(self._mditem, attribute)
218+
219+
def set_mditem_attribute_value(self, attribute: str, value: t.Any) -> bool:
220+
"""Set the raw MDItem attribute value without any type conversion.
221+
222+
Args:
223+
attribute: metadata attribute name
224+
value: value to set attribute to
225+
226+
Returns: True if successful otherwise False
227+
228+
Note: This is a low level function that you probably don't need to use,
229+
but may be useful in some cases. You should probably use the set() method instead.
230+
"""
231+
return MDItemSetAttribute(self._mditem, attribute, value)
232+
200233
@property
201234
def path(self) -> str:
202235
"""Return path to file"""

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "osxmetadata"
3-
version = "1.2.0"
3+
version = "1.2.1"
44
description = "Read and write meta data, such as tags/keywords, Finder comments, etc. on MacOS files"
55
authors = ["Rhet Turnbull <[email protected]>"]
66
license = "MIT License"

tests/test_cli.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,50 @@ def test_cli_set(test_file):
109109
assert md.description == "Goodbye World"
110110

111111

112+
def test_cli_set_multi_keywords_1(test_file):
113+
"""Test --set with multiple keywords (#83)"""
114+
115+
runner = CliRunner()
116+
result = runner.invoke(
117+
cli,
118+
[
119+
"--set",
120+
"keywords",
121+
"Foo",
122+
"--set",
123+
"keywords",
124+
"Bar",
125+
test_file.name,
126+
],
127+
)
128+
snooze()
129+
assert result.exit_code == 0
130+
md = OSXMetaData(test_file.name)
131+
assert sorted(md.keywords) == ["Bar", "Foo"]
132+
133+
134+
def test_cli_set_multi_keywords_2(test_file):
135+
"""Test --set, --append with multiple keywords (#83)"""
136+
137+
runner = CliRunner()
138+
result = runner.invoke(
139+
cli,
140+
[
141+
"--set",
142+
"keywords",
143+
"Foo",
144+
"--append",
145+
"keywords",
146+
"Bar",
147+
test_file.name,
148+
],
149+
)
150+
snooze()
151+
assert result.exit_code == 0
152+
md = OSXMetaData(test_file.name)
153+
assert sorted(md.keywords) == ["Bar", "Foo"]
154+
155+
112156
def test_cli_clear(test_file):
113157
"""Test --clear"""
114158

@@ -151,6 +195,52 @@ def test_cli_append(test_file):
151195
assert md.tags == [Tag("test", 0)]
152196

153197

198+
def test_cli_set_then_append(test_file):
199+
"""Test --set then --append"""
200+
201+
md = OSXMetaData(test_file.name)
202+
md.authors = ["John Doe"]
203+
204+
# set initial value
205+
runner = CliRunner()
206+
result = runner.invoke(
207+
cli,
208+
[
209+
"--set",
210+
"keywords",
211+
"foo",
212+
test_file.name,
213+
],
214+
)
215+
assert result.exit_code == 0
216+
217+
# set again and verify that it overwrites
218+
result = runner.invoke(
219+
cli,
220+
[
221+
"--set",
222+
"keywords",
223+
"bar",
224+
test_file.name,
225+
],
226+
)
227+
assert result.exit_code == 0
228+
assert md.keywords == ["bar"]
229+
230+
# append and verify that it appends
231+
result = runner.invoke(
232+
cli,
233+
[
234+
"--append",
235+
"keywords",
236+
"baz",
237+
test_file.name,
238+
],
239+
)
240+
assert result.exit_code == 0
241+
assert sorted(md.keywords) == ["bar", "baz"]
242+
243+
154244
def test_cli_get(test_file):
155245
"""Test --get"""
156246

tests/test_mditem_attributes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,13 @@ def test_mditem_attributes_audio(test_audio):
163163

164164
md = OSXMetaData(test_audio)
165165
assert md.get("kMDItemAudioSampleRate") == 44100.0
166+
167+
168+
def test_get_set_mditem_attribute_value(test_file):
169+
"""test get and set of mditem attribute value using the direct methods without value conversion, #83"""
170+
171+
md = OSXMetaData(test_file.name)
172+
md.set_mditem_attribute_value("kMDItemComment", "foo,bar")
173+
snooze()
174+
assert md.get_mditem_attribute_value("kMDItemComment") == "foo,bar"
175+
assert md.comment == "foo,bar"

0 commit comments

Comments
 (0)