Skip to content

Commit 209a1da

Browse files
committed
Merge branch 'release-0.6.2'
2 parents f497adc + ab7d89c commit 209a1da

17 files changed

Lines changed: 212 additions & 90 deletions

CHANGELOG

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
Trovebox Python Library Changelog
33
=================================
44

5+
v0.6.2
6+
======
7+
* Support Unicode tags (#74, #77)
8+
* Ensure lists inside parameters are UTF-8 encoded (#74, #77)
9+
* Fix repr unicode handling (#75)
10+
* Support unicode filenames (#72, #73)
11+
* Add Pypy to unit testing list (#78)
12+
513
v0.6.1
614
======
715
* Perform user expansion when uploading files from the CLI (#59, #70)

tests/data/test_ünicode_photo.jpg

1.62 KB
Loading

tests/functional/test_photos.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,27 @@ def test_upload_from_url(self):
121121
photos[1].delete()
122122
self.photos[0].update(permission=False)
123123

124+
# Unicode filename upload not working due to frontend bug 1433
125+
@unittest.expectedFailure
126+
def test_upload_unicode_filename(self):
127+
"""Test that a photo with a unicode filename can be uploaded"""
128+
ret_val = self.client.photo.upload(u"tests/data/test_\xfcnicode_photo.jpg",
129+
title=self.TEST_TITLE)
130+
# Check that there are now four photos
131+
self.photos = self.client.photos.list()
132+
self.assertEqual(len(self.photos), 4)
133+
134+
# Check that the upload return value was correct
135+
pathOriginals = [photo.pathOriginal for photo in self.photos]
136+
self.assertIn(ret_val.pathOriginal, pathOriginals)
137+
138+
# Delete the photo
139+
ret_val.delete()
140+
141+
# Check that it's gone
142+
self.photos = self.client.photos.list()
143+
self.assertEqual(len(self.photos), 3)
144+
124145
def test_update(self):
125146
""" Update a photo by editing the title """
126147
title = "\xfcmlaut" # umlauted umlaut
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Test File

tests/unit/test_activities.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ def test_zero_rows(self, mock_get):
7070

7171
@mock.patch.object(trovebox.Trovebox, 'get')
7272
def test_options(self, mock_get):
73-
"""Check that the activity list optionss are applied properly"""
73+
"""Check that the activity list options are applied properly"""
7474
mock_get.return_value = self._return_value(self.test_activities_dict)
7575
self.client.activities.list(options={"foo": "bar",
76-
"test1": "test2"},
76+
"test1": "\xfcmlaut"},
7777
foo="bar")
7878
# Dict element can be any order
7979
self.assertIn(mock_get.call_args[0],
80-
[("/activities/foo-bar/test1-test2/list.json",),
81-
("/activities/test1-test2/foo-bar/list.json",)])
80+
[("/activities/foo-bar/test1-%C3%BCmlaut/list.json",),
81+
("/activities/test1-%C3%BCmlaut/foo-bar/list.json",)])
8282
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
8383

8484
class TestActivitiesPurge(TestActivities):

tests/unit/test_cli.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def raise_exception(_):
2323

2424
class TestCli(unittest.TestCase):
2525
test_file = os.path.join("tests", "unit", "data", "test_file.txt")
26+
test_unicode_file = os.path.join("tests", "unit", "data",
27+
"\xfcnicode_test_file.txt")
2628

2729
@mock.patch.object(trovebox.main.trovebox, "Trovebox")
2830
@mock.patch('sys.stdout', new_callable=io.StringIO)
@@ -107,6 +109,45 @@ def test_post_missing_files(self, _, mock_trovebox):
107109
with self.assertRaises(IOError):
108110
main(["-X", "POST", "-F", "photo=@%s.missing" % self.test_file])
109111

112+
@mock.patch.object(trovebox.main.trovebox, "Trovebox")
113+
@mock.patch('sys.stdout', new_callable=io.StringIO)
114+
def test_post_unicode_files(self, _, mock_trovebox):
115+
"""Check that unicode filenames are posted correctly"""
116+
post = mock_trovebox.return_value.post
117+
118+
# Python 2.x provides encoded commandline arguments
119+
file_param = "photo=@%s" % self.test_unicode_file
120+
if sys.version < '3':
121+
file_param = file_param.encode(sys.getfilesystemencoding())
122+
123+
main(["-X", "POST", "-F", "photo=@%s" % self.test_unicode_file])
124+
# It's not possible to directly compare the file object,
125+
# so check it manually
126+
files = post.call_args[1]["files"]
127+
self.assertEqual(list(files.keys()), ["photo"])
128+
self.assertEqual(files["photo"].name, self.test_unicode_file)
129+
130+
@unittest.skipIf(sys.version >= '3',
131+
"Python3 only uses unicode commandline arguments")
132+
@mock.patch('trovebox.main.sys.getfilesystemencoding')
133+
@mock.patch.object(trovebox.main.trovebox, "Trovebox")
134+
@mock.patch('sys.stdout', new_callable=io.StringIO)
135+
def test_post_utf8_files(self, _, mock_trovebox, mock_getfilesystemencoding):
136+
"""Check that utf-8 encoded filenames are posted correctly"""
137+
post = mock_trovebox.return_value.post
138+
# Make the system think its filesystemencoding is utf-8
139+
mock_getfilesystemencoding.return_value = "utf-8"
140+
141+
file_param = "photo=@%s" % self.test_unicode_file
142+
file_param = file_param.encode("utf-8")
143+
144+
main(["-X", "POST", "-F", file_param])
145+
# It's not possible to directly compare the file object,
146+
# so check it manually
147+
files = post.call_args[1]["files"]
148+
self.assertEqual(list(files.keys()), ["photo"])
149+
self.assertEqual(files["photo"].name, self.test_unicode_file)
150+
110151
@mock.patch.object(sys, "exit", raise_exception)
111152
@mock.patch('sys.stderr', new_callable=io.StringIO)
112153
def test_unknown_arg(self, mock_stderr):

tests/unit/test_http.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,17 @@ def test_get_parameter_processing(self):
193193
self.client.get(self.test_endpoint,
194194
photo=photo, album=album, tag=tag,
195195
list_=[photo, album, tag],
196-
list2=["1", "2", "3"],
196+
list2=["1", False, 3],
197+
unicode_list=["1", "2", "\xfcmlaut"],
197198
boolean=True,
198199
unicode_="\xfcmlaut")
199200
params = self._last_request().querystring
200201
self.assertEqual(params["photo"], ["photo_id"])
201202
self.assertEqual(params["album"], ["album_id"])
202203
self.assertEqual(params["tag"], ["tag_id"])
203204
self.assertEqual(params["list_"], ["photo_id,album_id,tag_id"])
204-
self.assertEqual(params["list2"], ["1,2,3"])
205+
self.assertEqual(params["list2"], ["1,0,3"])
206+
self.assertIn(params["unicode_list"], [["1,2,\xc3\xbcmlaut"], ["1,2,\xfcmlaut"]])
205207
self.assertEqual(params["boolean"], ["1"])
206208
self.assertIn(params["unicode_"], [["\xc3\xbcmlaut"], ["\xfcmlaut"]])
207209

tests/unit/test_photos.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,27 +57,27 @@ def test_zero_rows(self, mock_get):
5757

5858
@mock.patch.object(trovebox.Trovebox, 'get')
5959
def test_options(self, mock_get):
60-
"""Check that the activity list options are applied properly"""
60+
"""Check that the photo list options are applied properly"""
6161
mock_get.return_value = self._return_value(self.test_photos_dict)
6262
self.client.photos.list(options={"foo": "bar",
63-
"test1": "test2"},
63+
"test1": "\xfcmlaut"},
6464
foo="bar")
6565
# Dict element can be any order
6666
self.assertIn(mock_get.call_args[0],
67-
[("/photos/foo-bar/test1-test2/list.json",),
68-
("/photos/test1-test2/foo-bar/list.json",)])
67+
[("/photos/foo-bar/test1-%C3%BCmlaut/list.json",),
68+
("/photos/test1-%C3%BCmlaut/foo-bar/list.json",)])
6969
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
7070

7171
class TestPhotosShare(TestPhotos):
7272
@mock.patch.object(trovebox.Trovebox, 'post')
7373
def test_photos_share(self, mock_post):
7474
self.client.photos.share(options={"foo": "bar",
75-
"test1": "test2"},
75+
"test1": "\xfcmlaut"},
7676
foo="bar")
7777
# Dict element can be any order
7878
self.assertIn(mock_post.call_args[0],
79-
[("/photos/foo-bar/test1-test2/share.json",),
80-
("/photos/test1-test2/foo-bar/share.json",)])
79+
[("/photos/foo-bar/test1-%C3%BCmlaut/share.json",),
80+
("/photos/test1-%C3%BCmlaut/foo-bar/share.json",)])
8181
self.assertEqual(mock_post.call_args[1], {"foo": "bar"})
8282

8383
class TestPhotosUpdate(TestPhotos):
@@ -363,12 +363,12 @@ def test_photo_view(self, mock_get):
363363
mock_get.return_value = self._return_value(self.test_photos_dict[1])
364364
result = self.client.photo.view(self.test_photos[0],
365365
options={"foo": "bar",
366-
"test1": "test2"},
366+
"test1": "\xfcmlaut"},
367367
returnSizes="20x20")
368368
# Dict elemet can be in any order
369369
self.assertIn(mock_get.call_args[0],
370-
[("/photo/1a/foo-bar/test1-test2/view.json",),
371-
("/photo/1a/test1-test2/foo-bar/view.json",)])
370+
[("/photo/1a/foo-bar/test1-%C3%BCmlaut/view.json",),
371+
("/photo/1a/test1-%C3%BCmlaut/foo-bar/view.json",)])
372372
self.assertEqual(mock_get.call_args[1], {"returnSizes": "20x20"})
373373
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
374374

@@ -378,13 +378,13 @@ def test_photo_view_id(self, mock_get):
378378
mock_get.return_value = self._return_value(self.test_photos_dict[1])
379379
result = self.client.photo.view("1a",
380380
options={"foo": "bar",
381-
"test1": "test2"},
381+
"test1": "\xfcmlaut"},
382382
returnSizes="20x20")
383383

384384
# Dict elemet can be in any order
385385
self.assertIn(mock_get.call_args[0],
386-
[("/photo/1a/foo-bar/test1-test2/view.json",),
387-
("/photo/1a/test1-test2/foo-bar/view.json",)])
386+
[("/photo/1a/foo-bar/test1-%C3%BCmlaut/view.json",),
387+
("/photo/1a/test1-%C3%BCmlaut/foo-bar/view.json",)])
388388
self.assertEqual(mock_get.call_args[1], {"returnSizes": "20x20"})
389389
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
390390

@@ -419,7 +419,7 @@ def test_photo_upload(self, mock_post):
419419
files = mock_post.call_args[1]["files"]
420420
self.assertEqual(endpoint, ("/photo/upload.json",))
421421
self.assertEqual(title, "Test")
422-
self.assertIn("photo", files)
422+
self.assertEqual(files["photo"].name, self.test_file)
423423
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
424424

425425
class TestPhotoUploadEncoded(TestPhotos):
@@ -455,12 +455,12 @@ def test_photo_next_previous(self, mock_get):
455455
"previous": [self.test_photos_dict[1]]})
456456
result = self.client.photo.next_previous(self.test_photos[0],
457457
options={"foo": "bar",
458-
"test1": "test2"},
458+
"test1": "\xfcmlaut"},
459459
foo="bar")
460460
# Dict elemet can be in any order
461461
self.assertIn(mock_get.call_args[0],
462-
[("/photo/1a/nextprevious/foo-bar/test1-test2.json",),
463-
("/photo/1a/nextprevious/test1-test2/foo-bar.json",)])
462+
[("/photo/1a/nextprevious/foo-bar/test1-%C3%BCmlaut.json",),
463+
("/photo/1a/nextprevious/test1-%C3%BCmlaut/foo-bar.json",)])
464464
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
465465
self.assertEqual(result["next"][0].get_fields(),
466466
self.test_photos_dict[0])
@@ -478,12 +478,12 @@ def test_photo_next_previous_id(self, mock_get):
478478
"previous": [self.test_photos_dict[1]]})
479479
result = self.client.photo.next_previous("1a",
480480
options={"foo": "bar",
481-
"test1": "test2"},
481+
"test1": "\xfcmlaut"},
482482
foo="bar")
483483
# Dict elemet can be in any order
484484
self.assertIn(mock_get.call_args[0],
485-
[("/photo/1a/nextprevious/foo-bar/test1-test2.json",),
486-
("/photo/1a/nextprevious/test1-test2/foo-bar.json",)])
485+
[("/photo/1a/nextprevious/foo-bar/test1-%C3%BCmlaut.json",),
486+
("/photo/1a/nextprevious/test1-%C3%BCmlaut/foo-bar.json",)])
487487
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
488488
self.assertEqual(result["next"][0].get_fields(),
489489
self.test_photos_dict[0])
@@ -500,12 +500,12 @@ def test_photo_object_next_previous(self, mock_get):
500500
{"next": [self.test_photos_dict[0]],
501501
"previous": [self.test_photos_dict[1]]})
502502
result = self.test_photos[0].next_previous(options={"foo": "bar",
503-
"test1": "test2"},
503+
"test1": "\xfcmlaut"},
504504
foo="bar")
505505
# Dict elemet can be in any order
506506
self.assertIn(mock_get.call_args[0],
507-
[("/photo/1a/nextprevious/foo-bar/test1-test2.json",),
508-
("/photo/1a/nextprevious/test1-test2/foo-bar.json",)])
507+
[("/photo/1a/nextprevious/foo-bar/test1-%C3%BCmlaut.json",),
508+
("/photo/1a/nextprevious/test1-%C3%BCmlaut/foo-bar.json",)])
509509
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
510510
self.assertEqual(result["next"][0].get_fields(),
511511
self.test_photos_dict[0])
@@ -606,6 +606,11 @@ def test_photo_object_repr_with_id_and_name(self):
606606
"name": "Test Name"})
607607
self.assertEqual(repr(photo), "<Photo name='Test Name'>")
608608

609+
def test_photo_object_repr_with_unicode_id(self):
610+
""" Ensure that a unicode id is correctly represented """
611+
photo = trovebox.objects.photo.Photo(self.client, {"id": "\xfcmlaut"})
612+
self.assertIn(repr(photo), [b"<Photo id='\xc3\xbcmlaut'>", "<Photo id='\xfcmlaut'>"])
613+
609614
@mock.patch.object(trovebox.Trovebox, 'post')
610615
def test_photo_object_create_attribute(self, _):
611616
"""

tests/unit/test_tags.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
class TestTags(unittest.TestCase):
1111
test_host = "test.example.com"
1212
test_tags = None
13-
test_tags_dict = [{"count": 11, "id":"tag1"},
14-
{"count": 5, "id":"tag2"}]
13+
test_tags_dict = [{"count": 11, "id": "tag1"},
14+
{"count": 5, "id": "tag2"}]
15+
16+
test_tag_unicode_dict = {"id": "\xfcmlaut"}
1517

1618
def setUp(self):
1719
self.client = trovebox.Trovebox(host=self.test_host)
1820
self.test_tags = [trovebox.objects.tag.Tag(self.client, tag)
1921
for tag in self.test_tags_dict]
22+
self.test_tag_unicode = trovebox.objects.tag.Tag(self.client,
23+
self.test_tag_unicode_dict)
2024

2125
@staticmethod
2226
def _return_value(result, message="", code=200):
@@ -89,6 +93,14 @@ def test_tag_object_delete(self, mock_post):
8993
self.assertEqual(tag.get_fields(), {})
9094
self.assertEqual(tag.id, None)
9195

96+
@mock.patch.object(trovebox.Trovebox, 'post')
97+
def test_tag_object_delete_unicode(self, mock_post):
98+
"""Check that a unicode tag can be deleted using its ID"""
99+
mock_post.return_value = self._return_value(True)
100+
result = self.client.tag.delete(self.test_tag_unicode)
101+
mock_post.assert_called_with("/tag/%C3%BCmlaut/delete.json")
102+
self.assertEqual(result, True)
103+
92104
class TestTagUpdate(TestTags):
93105
@mock.patch.object(trovebox.Trovebox, 'post')
94106
def test_tag_update(self, mock_post):
@@ -118,3 +130,11 @@ def test_tag_object_update(self, mock_post):
118130
self.assertEqual(tag.id, "tag2")
119131
self.assertEqual(tag.count, 5)
120132

133+
@mock.patch.object(trovebox.Trovebox, 'post')
134+
def test_tag_object_update_unicode(self, mock_post):
135+
"""Check that a unicode tag can be updated using its ID"""
136+
mock_post.return_value = self._return_value(self.test_tag_unicode_dict)
137+
result = self.client.tag.update(self.test_tag_unicode, name="Test")
138+
mock_post.assert_called_with("/tag/%C3%BCmlaut/update.json", name="Test")
139+
self.assertEqual(result.id, "\xfcmlaut")
140+

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py26, py27, py33, coverage
2+
envlist = py26, py27, py33, pypy, coverage
33

44
[testenv]
55
commands = python -m unittest discover tests/unit

0 commit comments

Comments
 (0)