From 0ddd814e7f333232a74228a29d6f1c10e1abdd52 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 16 May 2013 15:28:40 -0700 Subject: [PATCH 01/30] Allow `original_url` to behave as an attribute like `method` already does. Remove the `original_url` from the test to avoid confusion. Note: working with __getattr__ and __setattr__ this way is problematic. Those attributes exist on the Url class, not AttrDict. So while this is an improvement, it's perpetuating bad behavior. --- embedly/models.py | 6 +++--- embedly/tests.py | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/embedly/models.py b/embedly/models.py index 008bd43..3452c9d 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -27,15 +27,15 @@ def __init__(self, data=None): self.data = data def __getattr__(self, name): - if name in ['data', 'method']: + if name in ['data', 'method', 'original_url']: return object.__getattr__(self, name) try: return self.data[name] - except KeyError as e: + except KeyError: return None def __setattr__(self, name, value): - if name in ['data', 'method']: + if name in ['data', 'method', 'original_url']: object.__setattr__(self, name, value) else: self.data[name] = value diff --git a/embedly/tests.py b/embedly/tests.py index 0d74045..5305e02 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -22,7 +22,6 @@ def test_model(self): 'provider_url': 'http://www.google.com/', 'safe': True, 'description': 'Google', - 'original_url': 'http://google.com/', 'url': 'http://www.google.com/', 'type': 'html', 'object': {}, @@ -43,10 +42,10 @@ def test_model(self): obj = Url(data, 'preview', 'http://google.com/') - self.assertTrue(len(obj) is 17) - self.assertTrue(len(obj.values()) is 17) - self.assertTrue(len(obj.keys()) is 17) - self.assertTrue(len(obj.items()) is 17) + self.assertTrue(len(obj) is 16) + self.assertTrue(len(obj.values()) is 16) + self.assertTrue(len(obj.keys()) is 16) + self.assertTrue(len(obj.items()) is 16) self.assertTrue('type' in obj.keys()) self.assertTrue('html' in obj.values()) From ff6c7ee38b53ea6237aedf06bdd2e2713a2f1eab Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 16 May 2013 15:28:54 -0700 Subject: [PATCH 02/30] Simplify the __unicode__() method. --- embedly/models.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/embedly/models.py b/embedly/models.py index 3452c9d..76cae9f 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -67,10 +67,4 @@ def __str__(self): return self.__unicode__().encode("utf-8") def __unicode__(self): - r = '<%s ' % self.method.title() - - if self.original_url: - r += self.original_url - - r += ' >' - return r + return '<%s %s>' % (self.method.title(), self.original_url or "") From f084b71baf0a20558d5efc45ada867893127257c Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 16 May 2013 15:36:41 -0700 Subject: [PATCH 03/30] Pythonify the tests. - break out and add some new tests - use assertEqual() - use setUp() instead of __init__() - remove the ValueError on missing self.key that will never be reached - use assertRaises() for exception testing --- embedly/tests.py | 132 ++++++++++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/embedly/tests.py b/embedly/tests.py index 5305e02..7cf9fb9 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -6,19 +6,9 @@ class EmbedlyTestCase(unittest.TestCase): - - def __init__(self, *args, **kwargs): + def setUp(self): self.key = 'internal' - - if not self.key: - raise ValueError('Set envirnomental varible EMBEDLY_API_KEY ' + - 'before running these tests like so: $ export ' + - 'EMBEDLY_API_KEY=key') - - super(EmbedlyTestCase, self).__init__(*args, **kwargs) - - def test_model(self): - data = { + self.data = { 'provider_url': 'http://www.google.com/', 'safe': True, 'description': 'Google', @@ -40,35 +30,72 @@ def test_model(self): 'embeds': [] } - obj = Url(data, 'preview', 'http://google.com/') + def test_model(self): + obj = Url(self.data, 'preview', 'http://google.com/') - self.assertTrue(len(obj) is 16) - self.assertTrue(len(obj.values()) is 16) - self.assertTrue(len(obj.keys()) is 16) - self.assertTrue(len(obj.items()) is 16) + self.assertEqual(len(obj), 16) + self.assertEqual(len(obj.values()), 16) + self.assertEqual(len(obj.keys()), 16) + self.assertEqual(len(obj.items()), 16) + # look for expected data self.assertTrue('type' in obj.keys()) self.assertTrue('html' in obj.values()) - #Get the object - self.assertTrue(obj.type == 'html') - self.assertTrue(obj['type'] == 'html') - self.assertTrue(obj.get('type') == 'html') + # adding new data + obj['new_key'] = "new value" + self.assertEqual(len(obj), 17) + self.assertEqual(len(obj.items()), 17) + self.assertTrue('new_key' in obj.keys()) + self.assertTrue('new value' in obj.values()) - #nope - self.assertTrue(obj.nothing is None) + # our special attrs shouldn't be in the data dict + self.assertFalse('method' in obj.keys()) + self.assertFalse('original_url' in obj.keys()) - obj.nothing = 'something' - self.assertTrue(obj.nothing == 'something') + with self.assertRaises(KeyError): + obj['method'] + with self.assertRaises(KeyError): + obj['original_url'] + # attr access = dict access + obj.nothing = 'something' + self.assertEqual(obj['nothing'], 'something') obj['nothing'] = 'maybe' - self.assertTrue(obj['nothing'] == 'maybe') + self.assertEqual(obj.nothing, 'maybe') del obj['nothing'] self.assertTrue(obj.nothing is None) - #Deep Get attrs - self.assertTrue(obj.images[0].width is 275) + def test_model_attributes(self): + obj = Url(self.data, 'preview', 'http://original.url.com/') + + self.assertEqual(obj.method, 'preview') + self.assertEqual(obj.original_url, 'http://original.url.com/') + + def test_model_with_empty_data(self): + obj = Url() + + self.assertEqual(obj.data, {}) + self.assertEqual(obj.method, 'url') + self.assertTrue(obj.original_url is None) + + def test_model_data(self): + obj = Url(self.data, 'preview', 'http://google.com/') + + self.assertEqual(obj['type'], 'html') + self.assertEqual(obj.get('type'), 'html') + self.assertEqual(obj.data['type'], 'html') + self.assertEqual(obj.data.get('type'), 'html') + + obj['nothing'] = 'something' + self.assertEqual(obj['nothing'], 'something') + self.assertEqual(obj.get('nothing'), 'something') + self.assertEqual(obj.data['nothing'], 'something') + self.assertEqual(obj.data.get('nothing'), 'something') + + # deep get attrs + self.assertEqual(obj.images[0].width, 275) self.assertTrue(obj.images[0].nothing is None) self.assertTrue(obj.object.type is None) @@ -76,73 +103,70 @@ def test_provider(self): http = Embedly(self.key) obj = http.oembed('http://www.scribd.com/doc/13994900/Easter') - self.assertTrue(obj.provider_url == 'http://www.scribd.com/') + self.assertEqual(obj.provider_url, 'http://www.scribd.com/') obj = http.oembed('http://www.scribd.com/doc/28452730/Easter-Cards') - self.assertTrue(obj.provider_url == 'http://www.scribd.com/') + self.assertEqual(obj.provider_url, 'http://www.scribd.com/') obj = http.oembed('http://www.youtube.com/watch?v=Zk7dDekYej0') - self.assertTrue(obj.provider_url == 'http://www.youtube.com/') + self.assertEqual(obj.provider_url, 'http://www.youtube.com/') obj = http.oembed('http://yfrog.com/h22eu4j') - self.assertTrue(obj.provider_url == 'http://yfrog.com') + self.assertEqual(obj.provider_url, 'http://yfrog.com') def test_providers(self): http = Embedly(self.key) objs = list(http.oembed(['http://www.scribd.com/doc/13994900/Easter', 'http://www.scribd.com/doc/28452730/Easter-Cards'])) - self.assertTrue(objs[0].provider_url == 'http://www.scribd.com/') - self.assertTrue(objs[1].provider_url == 'http://www.scribd.com/') + self.assertEqual(objs[0].provider_url, 'http://www.scribd.com/') + self.assertEqual(objs[1].provider_url, 'http://www.scribd.com/') objs = list(http.oembed(['http://www.youtube.com/watch?v=Zk7dDekYej0', 'http://yfrog.com/h22eu4'])) - self.assertTrue(objs[0].provider_url == 'http://www.youtube.com/') - self.assertTrue(objs[1].provider_url == 'http://yfrog.com') + self.assertEqual(objs[0].provider_url, 'http://www.youtube.com/') + self.assertEqual(objs[1].provider_url, 'http://yfrog.com') def test_error(self): http = Embedly(self.key) obj = http.oembed('http://www.embedly.com/this/is/a/bad/url') - self.assertTrue(obj.error is True, obj.dict) + self.assertTrue(obj['error']) obj = http.oembed('http://blog.embed.ly/lsbsdlfldsf/asdfkljlas/klajsdlfkasdf') - self.assertTrue(obj.error is True, obj.dict) + self.assertTrue(obj['error']) obj = http.oembed('http://twitpic/nothing/to/see/here') - self.assertTrue(obj.error is True, obj.dict) + self.assertTrue(obj['error']) def test_multi_errors(self): http = Embedly(self.key) objs = list(http.oembed(['http://www.embedly.com/this/is/a/bad/url', 'http://blog.embed.ly/alsd/slsdlf/asdlfj'])) - self.assertTrue(objs[0].type == 'error', objs[0].dict) - self.assertTrue(objs[1].type == 'error', objs[1].dict) + self.assertEqual(objs[0].type, 'error') + self.assertEqual(objs[1].type, 'error') objs = list(http.oembed(['http://blog.embed.ly/lsbsdlfldsf/asdf/kl', 'http://twitpic.com/nothing/to/see/here'])) - self.assertTrue(objs[0].type == 'error',objs[0].dict) - self.assertTrue(objs[1].type == 'error',objs[1].dict) + self.assertEqual(objs[0].type, 'error') + self.assertEqual(objs[1].type, 'error') objs = list(http.oembed(['http://blog.embed.ly/lsbsdlfldsf/asdf/kl', 'http://yfrog.com/h22eu4j'])) - self.assertTrue(objs[0].type == 'error',objs[0].dict) - self.assertTrue(objs[1].type == 'photo',objs[1].dict) + self.assertEqual(objs[0].type, 'error') + self.assertEqual(objs[1].type, 'photo') objs = list(http.oembed(['http://yfrog.com/h22eu4j', 'http://www.scribd.com/asdf/asdf/asdfasdf'])) - self.assertTrue(objs[0].type == 'photo',objs[0].dict) - self.assertTrue(objs[1].type == 'error',objs[1].dict) + self.assertEqual(objs[0].type, 'photo') + self.assertEqual(objs[1].type, 'error') - def test_too_many_urls(self): + def test_exception_on_too_many_urls(self): http = Embedly(self.key) - urls = ['http://embed.ly'] * 21 - try: + + with self.assertRaises(ValueError): http.oembed(urls) - self.fail('too many urls, should have thrown an error') - except Exception as e: - self.assertTrue(type(e), ValueError) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From cc249ef244e50f07cd46c9e945ed17635edbee54 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 16 May 2013 16:57:42 -0700 Subject: [PATCH 04/30] Test to check the dict (and nested dictionaries) can be JSON serialized. This test fails. --- embedly/tests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/embedly/tests.py b/embedly/tests.py index 0d74045..4a036fe 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals import unittest +import json from embedly.client import Embedly from embedly.models import Url @@ -72,6 +73,10 @@ def test_model(self): self.assertTrue(obj.images[0].width is 275) self.assertTrue(obj.images[0].nothing is None) self.assertTrue(obj.object.type is None) + def test_model_data_can_serialize(self): + obj = Url({'a': {'key': 'value'}}) + unserialzed = json.loads(json.dumps(obj.data)) + self.assertDictEqual(obj.data, unserialzed) def test_provider(self): http = Embedly(self.key) From d2b9f8bd4a6ee70e7d46c88aacc4074f65a8e53f Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 16 May 2013 17:12:07 -0700 Subject: [PATCH 05/30] UserDict - no reason was given for reimplementing a core Python data structure. Furthermore, the custom AttrDict doesn't JSON serialize so an object built from JSON cannot be serialized back to JSON. **BREAKS BACKWARD COMPATIBILITY.** This is now a real Python dictionary and *no longer has attribute access* for the data. This could be added back, but it's un-pythonic and introduces complexity. UserDict retains the ability to access `data`. The `dict` property is gone. --- embedly/models.py | 67 +++++++---------------------------------------- embedly/tests.py | 66 ++++++++++++++++++---------------------------- 2 files changed, 34 insertions(+), 99 deletions(-) diff --git a/embedly/models.py b/embedly/models.py index 008bd43..0898679 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -1,65 +1,16 @@ -""" -Models - -Creates a sudo model class that makes it easy to access attributes -""" from __future__ import unicode_literals -class AttrDict(object): - """ - UserDict is a pain in the ass. Let's just make our own. - """ - def __init__(self, data=None): - if data is None: - data = {} - - for key, value in data.items(): - if isinstance(value, dict): - data[key] = AttrDict(value) - elif isinstance(value, list): - values = [] - for v in value: - if isinstance(v, dict): - values.append(AttrDict(v)) - else: - values.append(v) - data[key] = values - - self.data = data - - def __getattr__(self, name): - if name in ['data', 'method']: - return object.__getattr__(self, name) - try: - return self.data[name] - except KeyError as e: - return None +from UserDict import IterableUserDict - def __setattr__(self, name, value): - if name in ['data', 'method']: - object.__setattr__(self, name, value) - else: - self.data[name] = value - def __getitem__(self, name): return self.data[name] - def __setitem__(self, name, value): self.data[name] = value - def __delitem__(self, name): del self.data[name] - def __len__(self): return len(self.data) - def get(self, name): return self.data.get(name) - def keys(self): return self.data.keys() - def values(self): return self.data.values() - def items(self): return self.data.items() - - @property - def dict(self): - return self.data - - -class Url(AttrDict): +class Url(IterableUserDict, object): + """ + A dictionary with two additional attributes for the method and url. + UserDict provides a dictionary interface along with the regular + dictionary accsesible via the `data` attribute. - def __init__(self, data=None, method=None, original_url=None): - if data is None: - data = {} - super(Url, self).__init__(data) + """ + def __init__(self, data=None, method=None, original_url=None, **kwargs): + super(Url, self).__init__(data, **kwargs) self.method = method or 'url' self.original_url = original_url diff --git a/embedly/tests.py b/embedly/tests.py index 4a036fe..ae5295d 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -49,30 +49,14 @@ def test_model(self): self.assertTrue(len(obj.keys()) is 17) self.assertTrue(len(obj.items()) is 17) + # check for expected data self.assertTrue('type' in obj.keys()) self.assertTrue('html' in obj.values()) + self.assertEqual(obj['type'], 'html') + self.assertEqual(obj.get('type'), 'html') + self.assertEqual(obj.data['type'], 'html') + self.assertEqual(obj.data.get('type'), 'html') - #Get the object - self.assertTrue(obj.type == 'html') - self.assertTrue(obj['type'] == 'html') - self.assertTrue(obj.get('type') == 'html') - - #nope - self.assertTrue(obj.nothing is None) - - obj.nothing = 'something' - self.assertTrue(obj.nothing == 'something') - - obj['nothing'] = 'maybe' - self.assertTrue(obj['nothing'] == 'maybe') - - del obj['nothing'] - self.assertTrue(obj.nothing is None) - - #Deep Get attrs - self.assertTrue(obj.images[0].width is 275) - self.assertTrue(obj.images[0].nothing is None) - self.assertTrue(obj.object.type is None) def test_model_data_can_serialize(self): obj = Url({'a': {'key': 'value'}}) unserialzed = json.loads(json.dumps(obj.data)) @@ -82,62 +66,62 @@ def test_provider(self): http = Embedly(self.key) obj = http.oembed('http://www.scribd.com/doc/13994900/Easter') - self.assertTrue(obj.provider_url == 'http://www.scribd.com/') + self.assertEqual(obj['provider_url'], 'http://www.scribd.com/') obj = http.oembed('http://www.scribd.com/doc/28452730/Easter-Cards') - self.assertTrue(obj.provider_url == 'http://www.scribd.com/') + self.assertEqual(obj['provider_url'], 'http://www.scribd.com/') obj = http.oembed('http://www.youtube.com/watch?v=Zk7dDekYej0') - self.assertTrue(obj.provider_url == 'http://www.youtube.com/') + self.assertEqual(obj['provider_url'], 'http://www.youtube.com/') obj = http.oembed('http://yfrog.com/h22eu4j') - self.assertTrue(obj.provider_url == 'http://yfrog.com') + self.assertEqual(obj['provider_url'], 'http://yfrog.com') def test_providers(self): http = Embedly(self.key) objs = list(http.oembed(['http://www.scribd.com/doc/13994900/Easter', 'http://www.scribd.com/doc/28452730/Easter-Cards'])) - self.assertTrue(objs[0].provider_url == 'http://www.scribd.com/') - self.assertTrue(objs[1].provider_url == 'http://www.scribd.com/') + self.assertEqual(objs[0]['provider_url'], 'http://www.scribd.com/') + self.assertEqual(objs[1]['provider_url'], 'http://www.scribd.com/') objs = list(http.oembed(['http://www.youtube.com/watch?v=Zk7dDekYej0', 'http://yfrog.com/h22eu4'])) - self.assertTrue(objs[0].provider_url == 'http://www.youtube.com/') - self.assertTrue(objs[1].provider_url == 'http://yfrog.com') + self.assertEqual(objs[0]['provider_url'], 'http://www.youtube.com/') + self.assertEqual(objs[1]['provider_url'], 'http://yfrog.com') def test_error(self): http = Embedly(self.key) obj = http.oembed('http://www.embedly.com/this/is/a/bad/url') - self.assertTrue(obj.error is True, obj.dict) + self.assertTrue(obj['error']) obj = http.oembed('http://blog.embed.ly/lsbsdlfldsf/asdfkljlas/klajsdlfkasdf') - self.assertTrue(obj.error is True, obj.dict) + self.assertTrue(obj['error']) obj = http.oembed('http://twitpic/nothing/to/see/here') - self.assertTrue(obj.error is True, obj.dict) + self.assertTrue(obj['error']) def test_multi_errors(self): http = Embedly(self.key) objs = list(http.oembed(['http://www.embedly.com/this/is/a/bad/url', 'http://blog.embed.ly/alsd/slsdlf/asdlfj'])) - self.assertTrue(objs[0].type == 'error', objs[0].dict) - self.assertTrue(objs[1].type == 'error', objs[1].dict) + self.assertEqual(objs[0]['type'], 'error') + self.assertEqual(objs[1]['type'], 'error') objs = list(http.oembed(['http://blog.embed.ly/lsbsdlfldsf/asdf/kl', 'http://twitpic.com/nothing/to/see/here'])) - self.assertTrue(objs[0].type == 'error',objs[0].dict) - self.assertTrue(objs[1].type == 'error',objs[1].dict) + self.assertEqual(objs[0]['type'], 'error') + self.assertEqual(objs[1]['type'], 'error') objs = list(http.oembed(['http://blog.embed.ly/lsbsdlfldsf/asdf/kl', 'http://yfrog.com/h22eu4j'])) - self.assertTrue(objs[0].type == 'error',objs[0].dict) - self.assertTrue(objs[1].type == 'photo',objs[1].dict) + self.assertEqual(objs[0]['type'], 'error') + self.assertEqual(objs[1]['type'], 'photo') objs = list(http.oembed(['http://yfrog.com/h22eu4j', 'http://www.scribd.com/asdf/asdf/asdfasdf'])) - self.assertTrue(objs[0].type == 'photo',objs[0].dict) - self.assertTrue(objs[1].type == 'error',objs[1].dict) + self.assertEqual(objs[0]['type'], 'photo') + self.assertEqual(objs[1]['type'], 'error') def test_too_many_urls(self): http = Embedly(self.key) @@ -151,4 +135,4 @@ def test_too_many_urls(self): if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From 1127f06b1c5a1e736c386398c30bd8238b635390 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 16 May 2013 17:15:34 -0700 Subject: [PATCH 06/30] Add a test to ensure that object attributes don't collide with dictionary data. --- embedly/tests.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/embedly/tests.py b/embedly/tests.py index ae5295d..7aabd28 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -42,7 +42,7 @@ def test_model(self): 'embeds': [] } - obj = Url(data, 'preview', 'http://google.com/') + obj = Url(data, 'preview', 'http://original.url.com/') self.assertTrue(len(obj) is 17) self.assertTrue(len(obj.values()) is 17) @@ -57,6 +57,20 @@ def test_model(self): self.assertEqual(obj.data['type'], 'html') self.assertEqual(obj.data.get('type'), 'html') + # our special attrs shouldn't be in the data dict + self.assertFalse('method' in obj.keys()) + with self.assertRaises(KeyError): + obj['method'] + + # attrs and data dict values should be separate + self.assertEqual(obj['original_url'], 'http://google.com/') + self.assertEqual(obj.original_url, 'http://original.url.com/') + + obj.new_attr = 'attr value' + obj['new_key'] = 'dict value' + self.assertEqual(obj.new_attr, 'attr value') + self.assertEqual(obj['new_key'], 'dict value') + def test_model_data_can_serialize(self): obj = Url({'a': {'key': 'value'}}) unserialzed = json.loads(json.dumps(obj.data)) From 563de9def0930ecc3a7ab19bf4a67e3e78a50180 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Fri, 17 May 2013 11:38:32 -0700 Subject: [PATCH 07/30] Specify package version only once. Use it in the User Agent string (instead of the out-of-date, hardcoded version #). PEP8 setup.py. --- embedly/__init__.py | 4 +--- embedly/client.py | 5 +++-- setup.py | 26 ++++++++++++++------------ 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/embedly/__init__.py b/embedly/__init__.py index 3a083d7..b94f7ab 100644 --- a/embedly/__init__.py +++ b/embedly/__init__.py @@ -1,4 +1,2 @@ from __future__ import absolute_import -from .client import Embedly - -__version__ = '0.4.3' \ No newline at end of file +from .client import Embedly, __version__ diff --git a/embedly/client.py b/embedly/client.py index 9e2ab53..fbbbb8b 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -14,10 +14,11 @@ # py3k from urllib.parse import quote, urlencode - from .models import Url -USER_AGENT = 'Mozilla/5.0 (compatible; embedly-python/0.3;)' + +__version__ = '0.4.3' +USER_AGENT = 'Mozilla/5.0 (compatible; embedly-python/%s;)' % __version__ class Embedly(object): diff --git a/setup.py b/setup.py index e1a3578..1155c04 100644 --- a/setup.py +++ b/setup.py @@ -7,9 +7,11 @@ required = ['httplib2'] -if sys.version_info[:2] < (2,6): +if sys.version_info[:2] < (2, 6): required.append('simplejson') +version = __import__('embedly').__version__ + if os.path.exists("README.rst"): long_description = codecs.open("README.rst", "r", "utf-8").read() else: @@ -17,20 +19,20 @@ setup( - name = 'Embedly', - version = '0.4.3', - author = 'Embed.ly, Inc.', - author_email = 'support@embed.ly', - description = 'Python Library for Embedly', + name='Embedly', + version=version, + author='Embed.ly, Inc.', + author_email='support@embed.ly', + description='Python Library for Embedly', long_description=long_description, - license = """ + license=""" Copyright (c) 2011, Embed.ly, Inc. All rights reserved. Released under the 3-clause BSD license. """, - url = "https://github.com/embedly/embedly-python", - packages = ['embedly'], - install_requires = required, - zip_safe = True, + url="https://github.com/embedly/embedly-python", + packages=['embedly'], + install_requires=required, + zip_safe=True, classifiers=( 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', @@ -45,4 +47,4 @@ 'Programming Language :: Python :: 3.3', ), **extra -) \ No newline at end of file +) From 476e980c2a2ecc0753a1a3409a8ab0e57c4dd26d Mon Sep 17 00:00:00 2001 From: Sean Creeley Date: Mon, 17 Jun 2013 13:19:29 -0400 Subject: [PATCH 08/30] Update Readme --- README.rst | 39 +++++++++++++++++++++------------------ embedly/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/README.rst b/README.rst index c948954..74d9ee9 100644 --- a/README.rst +++ b/README.rst @@ -28,15 +28,13 @@ Here is a simple example and then we will go into the objects:: >>> from embedly import Embedly >>> client = Embedly(:key) >>> obj = client.oembed('http://instagr.am/p/BL7ti/') - >>> obj.type - u'photo' >>> obj['type'] u'photo' - >>> obj.url + >>> obj['url'] u'http://distillery.s3.amazonaws.com/media/2011/01/24/cdd759a319184cb79793506607ff5746_7.jpg' >>> obj = client.oembed('http://instagr.am/p/error') - >>> obj.error + >>> obj['error'] True Embedly Client @@ -53,12 +51,22 @@ The client object now has a bunch of different methods that you can use. ``oembed`` Corresponds to the `oEmbed endpoint - `_. Passes back a simple object that - allows you to retrieve a title, thumbnail, description and the embed html:: + `_. Passes back an object + that allows you to retrieve a title, thumbnail, description and the embed + html:: >>> client.oembed('http://vimeo.com/18150336') +``extract`` + Corresponds to the `Extract endpoint + `_. Passes back an + object that allows you to retrieve a title, description, content, html and a + list of images.:: + + >>> client.extract('http://vimeo.com/18150336') + + ``preview`` Corresponds to the `Preview endpoint `_. Passes back a simple object @@ -120,16 +128,11 @@ attributes. We will go through a few, but you should read the `documentation # Url Object can be accessed like a dictionary >>> obj['type'] u'video' - # Data can also be accessed like attributes - >> obj.type - u'video' - # Invalid attributes are returned as None - >>> obj.notanattribute - # The url object always has an ``orginal_url`` attrbiute. + # The url object always has an ``original_url`` attrbiute. >>> obj.original_url u'http://vimeo.com/18150336' - # The method used to retrive the URL is also on the obj + # The method used to retrieve the URL is also on the obj >>> obj.method u'oembed' @@ -137,9 +140,9 @@ For the Preview and Objectify endpoints the sub objects can also be accessed in the same manner. >>> obj = client.preview('http://vimeo.com/18150336', words=10) - >>> obj.object.type + >>> obj['object']['type'] u'video' - >>> obj.images[0].url + >>> obj['images'][0].url u'http://b.vimeocdn.com/ts/117/311/117311910_1280.jpg' Error Handling @@ -150,11 +153,11 @@ an error. For example if we use an invalid key, we will get a 401 response back >>> client = Embedly('notakey') >>> obj = client.preview('http://vimeo.com/18150336', words=10) - >>> obj.error + >>> obj['error'] True - >>> obj.error_code + >>> obj['error_code'] 401 Copyright --------- -Copyright (c) 2011 Embed.ly, Inc. See LICENSE for details. \ No newline at end of file +Copyright (c) 2013 Embed.ly, Inc. See LICENSE for details. \ No newline at end of file diff --git a/embedly/__init__.py b/embedly/__init__.py index 3a083d7..ec9ae17 100644 --- a/embedly/__init__.py +++ b/embedly/__init__.py @@ -1,4 +1,4 @@ from __future__ import absolute_import from .client import Embedly -__version__ = '0.4.3' \ No newline at end of file +__version__ = '0.4.4' diff --git a/setup.py b/setup.py index e1a3578..1b5ce74 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( name = 'Embedly', - version = '0.4.3', + version = '0.4.4', author = 'Embed.ly, Inc.', author_email = 'support@embed.ly', description = 'Python Library for Embedly', From 40be69dbfea07836f3e6e1a899ef7eab6c00cca4 Mon Sep 17 00:00:00 2001 From: Bob Corsaro Date: Mon, 17 Jun 2013 13:33:41 -0400 Subject: [PATCH 09/30] Fixes for python3 --- embedly/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/embedly/models.py b/embedly/models.py index 1106fbc..5428990 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -1,6 +1,9 @@ from __future__ import unicode_literals -from UserDict import IterableUserDict +try: + from UserDict import IterableUserDict +except ImportError: + from collections import UserDict as IterableUserDict class Url(IterableUserDict, object): """ From b50aa17b7a7bca63ceb58c88d90e6db28df98e62 Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Wed, 28 Aug 2013 11:35:49 -0400 Subject: [PATCH 10/30] Fix JSON serialization --- embedly/models.py | 6 ++++++ embedly/tests.py | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/embedly/models.py b/embedly/models.py index 5428990..e0826f0 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -22,3 +22,9 @@ def __str__(self): def __unicode__(self): return '<%s %s>' % (self.method.title(), self.original_url or "") + + def __getitem__(self, key): + if (key in self.data) and (self.data[key] == None): + return '' + else: + return self.data[key] diff --git a/embedly/tests.py b/embedly/tests.py index 3a7c168..d8b86ce 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -61,7 +61,13 @@ def test_model(self): self.assertEqual(obj['new_key'], 'dict value') def test_model_data_can_serialize(self): - obj = Url({'a': {'key': 'value'}}) + obj = Url({'hash': {'key': 'value'}, + 'none': None, + 'empty': '', + 'float': 1.234, + 'int': 1, + 'string': 'string', + 'array': [0, -1]}) unserialzed = json.loads(json.dumps(obj.data)) self.assertDictEqual(obj.data, unserialzed) From 251116888237a6ef99ebfdc36696799d31c88490 Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Thu, 29 Aug 2013 13:57:39 -0400 Subject: [PATCH 11/30] Remove unneeded override of Url#__get_item__ --- embedly/models.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/embedly/models.py b/embedly/models.py index e0826f0..5428990 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -22,9 +22,3 @@ def __str__(self): def __unicode__(self): return '<%s %s>' % (self.method.title(), self.original_url or "") - - def __getitem__(self, key): - if (key in self.data) and (self.data[key] == None): - return '' - else: - return self.data[key] From 4b40857760a8851f74b3323a645d1c72bfcc8828 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 29 Aug 2013 12:50:08 -0700 Subject: [PATCH 12/30] Fix __version__ import. There were circular import issues most notably during install. `setup.py` now grabs the version information without having to import the module (which led to importing other modules which isn't appropriate during setup.py activities). --- embedly/__init__.py | 4 +++- embedly/client.py | 9 +++++---- setup.py | 8 ++++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/embedly/__init__.py b/embedly/__init__.py index b94f7ab..d0161a8 100644 --- a/embedly/__init__.py +++ b/embedly/__init__.py @@ -1,2 +1,4 @@ from __future__ import absolute_import -from .client import Embedly, __version__ +from .client import Embedly + +__version__ = '0.4.3' diff --git a/embedly/client.py b/embedly/client.py index fbbbb8b..6c10f74 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -17,8 +17,9 @@ from .models import Url -__version__ = '0.4.3' -USER_AGENT = 'Mozilla/5.0 (compatible; embedly-python/%s;)' % __version__ +def get_user_agent(): + from . import __version__ + return 'Mozilla/5.0 (compatible; embedly-python/%s;)' % __version__ class Embedly(object): @@ -26,7 +27,7 @@ class Embedly(object): Client """ - def __init__(self, key=None, user_agent=USER_AGENT, timeout=60): + def __init__(self, key=None, user_agent=None, timeout=60): """ Initialize the Embedly client @@ -37,7 +38,7 @@ def __init__(self, key=None, user_agent=USER_AGENT, timeout=60): :returns: None """ - self.user_agent = user_agent + self.user_agent = user_agent or get_user_agent() self.timeout = timeout self.key = key self.services = [] diff --git a/setup.py b/setup.py index 1155c04..b0db388 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,11 @@ if sys.version_info[:2] < (2, 6): required.append('simplejson') -version = __import__('embedly').__version__ +def get_version(): + with open(os.path.join('embedly', '__init__.py')) as f: + for line in f: + if line.startswith('__version__ ='): + return line.split('=')[1].strip().strip('"\'') if os.path.exists("README.rst"): long_description = codecs.open("README.rst", "r", "utf-8").read() @@ -20,7 +24,7 @@ setup( name='Embedly', - version=version, + version=get_version(), author='Embed.ly, Inc.', author_email='support@embed.ly', description='Python Library for Embedly', From 78486817eb2cff81df079edea1a9a0c87fef8c6f Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 29 Aug 2013 12:56:37 -0700 Subject: [PATCH 13/30] Tidying - reorder params and add a documentation line for third param. --- embedly/client.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/embedly/client.py b/embedly/client.py index 6c10f74..9ad81f8 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -31,18 +31,20 @@ def __init__(self, key=None, user_agent=None, timeout=60): """ Initialize the Embedly client - :param user_agent: User Agent passed to Embedly - :type user_agent: str :param key: Embedly Pro key :type key: str + :param user_agent: User Agent passed to Embedly + :type user_agent: str + :param timeout: timeout for HTTP connection attempts + :type timeout: int :returns: None """ + self.key = key self.user_agent = user_agent or get_user_agent() self.timeout = timeout - self.key = key - self.services = [] + self.services = [] self._regex = None def get_services(self): From 59048dbf7ee7e4138fe20146bf83705097efabfb Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 14 Nov 2013 21:16:33 -0800 Subject: [PATCH 14/30] Fix testing in Python 2.6 --- embedly/tests.py | 9 +++++---- setup.py | 5 +++++ tox.ini | 5 ++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/embedly/tests.py b/embedly/tests.py index d8b86ce..32a46a1 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -1,7 +1,11 @@ from __future__ import unicode_literals -import unittest import json +try: + import unittest2 as unittest # Python 2.6 +except ImportError: + import unittest + from embedly.client import Embedly from embedly.models import Url @@ -87,7 +91,6 @@ def test_provider(self): obj = http.oembed('http://yfrog.com/h22eu4j') self.assertEqual(obj['provider_url'], 'http://yfrog.com') - def test_providers(self): http = Embedly(self.key) @@ -102,7 +105,6 @@ def test_providers(self): self.assertEqual(objs[0]['provider_url'], 'http://www.youtube.com/') self.assertEqual(objs[1]['provider_url'], 'http://yfrog.com') - def test_error(self): http = Embedly(self.key) @@ -137,7 +139,6 @@ def test_multi_errors(self): self.assertEqual(objs[0]['type'], 'photo') self.assertEqual(objs[1]['type'], 'error') - def test_exception_on_too_many_urls(self): http = Embedly(self.key) urls = ['http://embed.ly'] * 21 diff --git a/setup.py b/setup.py index b0db388..b687352 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,10 @@ extra = {} required = ['httplib2'] +tests_require = [] + +if sys.version_info[:2] < (2, 7): + tests_require.append('unittest2') if sys.version_info[:2] < (2, 6): required.append('simplejson') @@ -36,6 +40,7 @@ def get_version(): url="https://github.com/embedly/embedly-python", packages=['embedly'], install_requires=required, + tests_require=tests_require, zip_safe=True, classifiers=( 'Development Status :: 5 - Production/Stable', diff --git a/tox.ini b/tox.ini index 2ffa83b..a89c03c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,7 @@ [tox] envlist = py26,py27,py31,py32,py33 [testenv] -commands=python embedly/tests.py \ No newline at end of file +commands=python embedly/tests.py + +[testenv:py26] +deps = unittest2 From 2931368d8689e84ba15da1f0cfbfe3398cf33110 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 14 Nov 2013 21:27:26 -0800 Subject: [PATCH 15/30] Drop Python 3.1 support. I'm not sure this *was* working in Python 3.1, but TravisCI isn't testing it and Tox coughs up an `ImportError: No module named ssl_match_hostname`. We can look into this more, but supporting 3.1 probably isn't worth it. --- setup.py | 1 - tox.ini | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b687352..8a7ecd7 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,6 @@ def get_version(): 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', ), diff --git a/tox.ini b/tox.ini index a89c03c..eee5948 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,6 @@ [tox] -envlist = py26,py27,py31,py32,py33 +envlist = py26,py27,py32,py33 + [testenv] commands=python embedly/tests.py From 336950224f449876186791fa1c98002095a39545 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 14 Nov 2013 21:30:39 -0800 Subject: [PATCH 16/30] close our HTTP request connections Good practice and Python 3.2/3.3 had this to say, `embedly\client.py:152: ResourceWarning: unclosed ` --- embedly/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/embedly/client.py b/embedly/client.py index 9ad81f8..12d4306 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -58,7 +58,8 @@ def get_services(self): url = 'http://api.embed.ly/1/services/python' http = httplib2.Http(timeout=self.timeout) - headers = {'User-Agent' : self.user_agent} + headers = {'User-Agent': self.user_agent, + 'Connection': 'close'} resp, content = http.request(url, headers=headers) if resp['status'] == '200': @@ -128,7 +129,8 @@ def _get(self, version, method, url_or_urls, **kwargs): http = httplib2.Http(timeout=self.timeout) - headers = {'User-Agent': self.user_agent} + headers = {'User-Agent': self.user_agent, + 'Connection': 'close'} resp, content = http.request(url, headers=headers) From 9c7ba5eaf31b0ed8ff45d6ee28f5fee6c825a530 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 14 Nov 2013 21:34:59 -0800 Subject: [PATCH 17/30] Tidying. Additional gitignore lines. Don't need the conditional `simplejson` because we don't support Python 2.5. --- .gitignore | 3 ++- embedly/client.py | 13 +++++++------ setup.py | 7 +------ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index c18dd39..45bfe99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.pyc -Embedly.egg-info/ +*.egg-info/ +.tox/ build/ dist/ .virtualenv diff --git a/embedly/client.py b/embedly/client.py index 12d4306..872bb83 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -53,7 +53,8 @@ def get_services(self): the list of supported providers and their regexes """ - if self.services: return self.services + if self.services: + return self.services url = 'http://api.embed.ly/1/services/python' @@ -66,10 +67,10 @@ def get_services(self): resp_data = json.loads(content) self.services = resp_data - #build the regex that we can use later. + # build the regex that we can use later _regex = [] for each in self.get_services(): - _regex.append('|'.join(each.get('regex',[]))) + _regex.append('|'.join(each.get('regex', []))) self._regex = re.compile('|'.join(_regex)) @@ -100,10 +101,10 @@ def _get(self, version, method, url_or_urls, **kwargs): raise ValueError('%s requires a url or a list of urls given: %s' % (method.title(), url_or_urls)) - #A flag we can use instead of calling isinstance all the time. + # a flag we can use instead of calling isinstance() all the time multi = isinstance(url_or_urls, list) - # Throw an error early for too many URLs + # throw an error early for too many URLs if multi and len(url_or_urls) > 20: raise ValueError('Embedly accepts only 20 urls at a time. Url ' 'Count:%s' % len(url_or_urls)) @@ -112,7 +113,7 @@ def _get(self, version, method, url_or_urls, **kwargs): key = kwargs.get('key', self.key) - #make sure that a key was set on the client or passed in. + # make sure that a key was set on the client or passed in if not key: raise ValueError('Requires a key. None given: %s' % key) diff --git a/setup.py b/setup.py index 8a7ecd7..0ed310d 100644 --- a/setup.py +++ b/setup.py @@ -3,16 +3,12 @@ import codecs from setuptools import setup -extra = {} - required = ['httplib2'] tests_require = [] if sys.version_info[:2] < (2, 7): tests_require.append('unittest2') -if sys.version_info[:2] < (2, 6): - required.append('simplejson') def get_version(): with open(os.path.join('embedly', '__init__.py')) as f: @@ -53,6 +49,5 @@ def get_version(): 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', - ), - **extra + ) ) From 1fedeb460988db7328fa8205b6b1aacd85e91663 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 14 Nov 2013 22:43:56 -0800 Subject: [PATCH 18/30] Update Readme Mostly this updates the `Url` model documentation to reflect its more pythonic nature as a true dictionary. Also update a couple links, don't use `sudo` and fix the RST formatting for a few code blocks. --- README.rst | 87 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/README.rst b/README.rst index 74d9ee9..b7dc81d 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ embedly-python ============== -Python Library for interacting with Embedly's API. To get started sign up for -a key at `embed.ly/signup `_. +Python library for interacting with Embedly's API. To get started sign up for +a key at `embed.ly/signup `_. Install ------- @@ -9,43 +9,47 @@ Install with `Pip `_ (recommended):: pip install embedly -Or easy_install +Or easy_install:: - sudo easy_install Embedly + easy_install Embedly Or setuptools:: git clone git://github.com/embedly/embedly-python.git - sudo python setup.py + python setup.py Getting Started --------------- This library is meant to be a dead simple way to interact with the Embedly API. -There are only 2 main objects, the ``Embedly`` client and the ``Url`` model. -Here is a simple example and then we will go into the objects:: +There are only 2 main objects, the ``Embedly`` client and the ``Url`` response +model. Here is a simple example and then we will go into the objects:: >>> from embedly import Embedly >>> client = Embedly(:key) - >>> obj = client.oembed('http://instagr.am/p/BL7ti/') + >>> obj = client.oembed('http://instagram.com/p/BL7ti/') >>> obj['type'] u'photo' >>> obj['url'] - u'http://distillery.s3.amazonaws.com/media/2011/01/24/cdd759a319184cb79793506607ff5746_7.jpg' + u'http://images.ak.instagram.com/media/2011/01/24/cdd759a319184cb79793506607ff5746_7.jpg' - >>> obj = client.oembed('http://instagr.am/p/error') + >>> obj = client.oembed('http://instagram.com/error/error/') >>> obj['error'] True Embedly Client """""""""""""" -The Embedly client is a object that takes in a key and an optional User Agent -then handles all the interactions and HTTP requests to Embedly. To initialize -the object pass in your key you got from signing up for Embedly and an optional -User Agent. +The Embedly client is a object that takes in a key and optional User Agent +and timeout parameters then handles all the interactions and HTTP requests +to Embedly. To initialize the object, you'll need the key that you got when +you signed up for Embedly. +:: >>> from embedly import Embedly - >>> client = Embedly('key', 'Mozilla/5.0 (compatible; example-org;)') + >>> client = Embedly('key') + >>> client2 = Embedly('key', 'Mozilla/5.0 (compatible; example-org;)') + >>> client3 = Embedly('key', 'Mozilla/5.0 (compatible; example-org;)', 30) + >>> client4 = Embedly('key', timeout=10, user_agent='Mozilla/5.0 (compatible; example-org;)') The client object now has a bunch of different methods that you can use. @@ -92,7 +96,7 @@ keyword arguments that correspond to Embedly's `query arguments >>> client.oembed(['http://vimeo.com/18150336', 'http://www.youtube.com/watch?v=hD7ydlyhvKs'], maxwidth=500, words=20) -There are some supporting functions that allow you to limit urls before sending +There are some supporting functions that allow you to limit URLs before sending them to Embedly. Embedly can return metadata for any URL, these just allow a developer to only pass a subset of Embedly `providers `_. Note that URL shorteners like bit.ly or t.co are @@ -116,43 +120,60 @@ not supported through these regexes. Url Object """""""""" -The ``Url`` Object is just a smart dictionary that acts more like an object. -For example when you run ``oembed`` you get back a Url Object: +The ``Url`` object is basically a response dictionary returned from +one of the Embedly API endpoints. +:: - >>> obj = client.oembed('http://vimeo.com/18150336', words=10) + >>> response = client.oembed('http://vimeo.com/18150336', words=10) -Depending on the method you are using, the object has a different set of +Depending on the method you are using, the response will have different attributes. We will go through a few, but you should read the `documentation -`_ to get the full list of data that is passed back.:: +`_ to get the full list of data that is passed back. +:: - # Url Object can be accessed like a dictionary - >>> obj['type'] + >>> response['type'] u'video' + >>> response['title'] + u'Wingsuit Basejumping - The Need 4 Speed: The Art of Flight' + >>> response['provider_name'] + u'Vimeo' + >>> response['width'] + 1280 + +As you can see the ``Url`` object works like a dictionary, but it's slightly +enhanced. It will always have ``method`` and ``original_url`` attributes, +which represent the Embedly request type and the URL requested. +:: + + >>> response.method + 'oembed' + >>> response.original_url + 'http://vimeo.com/18150336' - # The url object always has an ``original_url`` attrbiute. - >>> obj.original_url - u'http://vimeo.com/18150336' - # The method used to retrieve the URL is also on the obj - >>> obj.method - u'oembed' + # useful because the response data itself may not have a URL + # (or it could have a redirected link, querystring params, etc) + >>> response['url'] + ... + KeyError: 'url' -For the Preview and Objectify endpoints the sub objects can also be accessed in +For the Preview and Objectify endpoints the sub-objects can also be accessed in the same manner. +:: >>> obj = client.preview('http://vimeo.com/18150336', words=10) >>> obj['object']['type'] u'video' - >>> obj['images'][0].url + >>> obj['images'][0]['url'] u'http://b.vimeocdn.com/ts/117/311/117311910_1280.jpg' Error Handling -------------- -If there was an error processing the request, The ``Url`` object will contain +If there was an error processing the request, the ``Url`` object will contain an error. For example if we use an invalid key, we will get a 401 response back :: >>> client = Embedly('notakey') - >>> obj = client.preview('http://vimeo.com/18150336', words=10) + >>> obj = client.preview('http://vimeo.com/18150336') >>> obj['error'] True >>> obj['error_code'] From 24db8ef42f9410cf342f11e3f66d3c8c954e5cd6 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 21 Nov 2013 14:46:41 -0800 Subject: [PATCH 19/30] Remove a working but unnecessary recursive call. --- embedly/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embedly/client.py b/embedly/client.py index 872bb83..6fbd5ba 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -69,7 +69,7 @@ def get_services(self): # build the regex that we can use later _regex = [] - for each in self.get_services(): + for each in self.services: _regex.append('|'.join(each.get('regex', []))) self._regex = re.compile('|'.join(_regex)) From 131cfb3e97f42916ee1e8d7f362679d566db6533 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 21 Nov 2013 14:51:34 -0800 Subject: [PATCH 20/30] Fix an issue when calling `get_services()` in Python 3. (This now matches the response decoding already used in `_get()`. It was never noticed because tests didn't cover it.) --- embedly/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embedly/client.py b/embedly/client.py index 6fbd5ba..288e8a5 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -64,7 +64,7 @@ def get_services(self): resp, content = http.request(url, headers=headers) if resp['status'] == '200': - resp_data = json.loads(content) + resp_data = json.loads(content.decode('utf-8')) self.services = resp_data # build the regex that we can use later From e1f11b46e20aee04407fb012366638f0017bc37c Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 21 Nov 2013 14:54:41 -0800 Subject: [PATCH 21/30] Increase test coverage. Reorder imports so that Python 3 is first. --- embedly/client.py | 10 ++--- embedly/models.py | 9 +++-- embedly/tests.py | 101 +++++++++++++++++++++++++++++++++++++++------- 3 files changed, 96 insertions(+), 24 deletions(-) diff --git a/embedly/client.py b/embedly/client.py index 288e8a5..b78aeed 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -8,11 +8,11 @@ import re import httplib2 import json -try: - from urllib import quote, urlencode -except ImportError: - # py3k - from urllib.parse import quote, urlencode + +try: # pragma: no cover + from urllib.parse import quote, urlencode # pragma: no cover +except ImportError: # Python 2 # pragma: no cover + from urllib import quote, urlencode # pragma: no cover from .models import Url diff --git a/embedly/models.py b/embedly/models.py index 5428990..d347ef1 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -1,9 +1,10 @@ from __future__ import unicode_literals -try: - from UserDict import IterableUserDict -except ImportError: - from collections import UserDict as IterableUserDict +try: # pragma: no cover + from collections import UserDict as IterableUserDict # pragma: no cover +except ImportError: # Python 2 # pragma: no cover + from UserDict import IterableUserDict # pragma: no cover + class Url(IterableUserDict, object): """ diff --git a/embedly/tests.py b/embedly/tests.py index 32a46a1..d236d73 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -1,19 +1,17 @@ from __future__ import unicode_literals +import re import json -try: - import unittest2 as unittest # Python 2.6 -except ImportError: - import unittest +try: # pragma: no cover + import unittest2 as unittest # Python 2.6 # pragma: no cover +except ImportError: # pragma: no cover + import unittest # pragma: no cover from embedly.client import Embedly from embedly.models import Url -class EmbedlyTestCase(unittest.TestCase): - def setUp(self): - self.key = 'internal' - +class UrlTestCase(unittest.TestCase): def test_model(self): data = { 'provider_url': 'http://www.google.com/', @@ -75,6 +73,24 @@ def test_model_data_can_serialize(self): unserialzed = json.loads(json.dumps(obj.data)) self.assertDictEqual(obj.data, unserialzed) + +class EmbedlyTestCase(unittest.TestCase): + def setUp(self): + self.key = 'internal' + + def test_requires_api_key(self): + with self.assertRaises(ValueError): + Embedly()._get(1, "test", "http://fake") + + def test_requires_url(self): + with self.assertRaises(ValueError): + Embedly(self.key)._get(1, "test", None) + + def test_exception_on_too_many_urls(self): + urls = ['http://embed.ly'] * 21 + with self.assertRaises(ValueError): + Embedly(self.key)._get(1, "test", urls) + def test_provider(self): http = Embedly(self.key) @@ -139,13 +155,68 @@ def test_multi_errors(self): self.assertEqual(objs[0]['type'], 'photo') self.assertEqual(objs[1]['type'], 'error') - def test_exception_on_too_many_urls(self): - http = Embedly(self.key) - urls = ['http://embed.ly'] * 21 + def test_raw_content_in_request(self): + client = Embedly(self.key) + response = client.oembed( + 'http://www.scribd.com/doc/13994900/Easter', + raw=True) - with self.assertRaises(ValueError): - http.oembed(urls) + self.assertEqual(response['raw'], response.data['raw']) + + parsed = json.loads(response['raw'].decode('utf-8')) + self.assertEqual(response['type'], parsed['type']) + + def test_regex_url_matches(self): + regex = [ + 'http://.*youtube\\.com/watch.*', + 'http://www\\.vimeo\\.com/.*'] + client = Embedly(self.key) + client._regex = re.compile('|'.join(regex)) + + self.assertTrue( + client.is_supported('http://www.youtube.com/watch?v=Zk7dDekYej0')) + self.assertTrue( + client.is_supported('http://www.vimeo.com/18150336')) + self.assertFalse( + client.is_supported('http://vimeo.com/18150336')) + self.assertFalse( + client.is_supported('http://yfrog.com/h22eu4j')) + + def test_services_can_be_manually_configured(self): + client = Embedly(self.key) + client.services = ['nothing', 'like', 'real', 'response', 'data'] + + self.assertTrue('nothing' in client.get_services()) + self.assertEqual(len(client.get_services()), 5) + + def test_get_services_retrieves_data_and_builds_regex(self): + client = Embedly(self.key) + client.get_services() + + self.assertGreater(len(client.services), 0) + self.assertTrue(client.regex.match('http://yfrog.com/h22eu4j')) + + def test_extract(self): + client = Embedly(self.key) + response = client.extract('http://vimeo.com/18150336') + + self.assertEqual(response.method, 'extract') + self.assertEqual(response['provider_name'], 'Vimeo') + + def test_preview(self): + client = Embedly(self.key) + response = client.preview('http://vimeo.com/18150336') + + self.assertEqual(response.method, 'preview') + self.assertEqual(response['provider_name'], 'Vimeo') + + def test_objectify(self): + client = Embedly(self.key) + response = client.objectify('http://vimeo.com/18150336') + + self.assertEqual(response.method, 'objectify') + self.assertEqual(response['provider_name'], 'Vimeo') -if __name__ == '__main__': - unittest.main() +if __name__ == '__main__': # pragma: no cover + unittest.main() # pragma: no cover From 4de59b7e690894d08ed5bbe7a4e63453f2b1cfff Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 21 Nov 2013 15:20:03 -0800 Subject: [PATCH 22/30] Tests - add mocking to test `get_services()` This may be overkill but it does increase test coverage and if we develop more fine-grained tests for `_get()` in the future, mock will help. --- embedly/tests.py | 23 +++++++++++++++++++++++ setup.py | 3 +++ tox.ini | 10 +++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/embedly/tests.py b/embedly/tests.py index d236d73..227988f 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -7,6 +7,11 @@ except ImportError: # pragma: no cover import unittest # pragma: no cover +try: # pragma: no cover + from unittest import mock # pragma: no cover +except ImportError: # Python < 3.3 # pragma: no cover + import mock # pragma: no cover + from embedly.client import Embedly from embedly.models import Url @@ -182,6 +187,14 @@ def test_regex_url_matches(self): self.assertFalse( client.is_supported('http://yfrog.com/h22eu4j')) + @mock.patch.object(Embedly, 'get_services') + def test_regex_access_triggers_get_services(self, mock_services): + client = Embedly(self.key) + client.regex + + self.assertTrue(mock_services.called) + self.assertIsNone(client._regex) + def test_services_can_be_manually_configured(self): client = Embedly(self.key) client.services = ['nothing', 'like', 'real', 'response', 'data'] @@ -189,6 +202,16 @@ def test_services_can_be_manually_configured(self): self.assertTrue('nothing' in client.get_services()) self.assertEqual(len(client.get_services()), 5) + @mock.patch('httplib2.Http', autospec=True) + def test_services_remains_empty_on_failed_http(self, MockHttp): + MockHttp.return_value.request.return_value = ({'status': 500}, "") + + client = Embedly(self.key) + client.get_services() + + self.assertFalse(client.services) + self.assertTrue(MockHttp.return_value.request.called) + def test_get_services_retrieves_data_and_builds_regex(self): client = Embedly(self.key) client.get_services() diff --git a/setup.py b/setup.py index 0ed310d..acb908c 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,9 @@ if sys.version_info[:2] < (2, 7): tests_require.append('unittest2') +if sys.version_info[:2] < (3, 3): + tests_require.append('mock') + def get_version(): with open(os.path.join('embedly', '__init__.py')) as f: diff --git a/tox.ini b/tox.ini index eee5948..e039cb9 100644 --- a/tox.ini +++ b/tox.ini @@ -5,4 +5,12 @@ envlist = py26,py27,py32,py33 commands=python embedly/tests.py [testenv:py26] -deps = unittest2 +deps = + mock + unittest2 + +[testenv:py27] +deps = mock + +[testenv:py32] +deps = mock From b99d01ba732a6ea158e12f1538f66817d5d0b272 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 21 Nov 2013 15:52:59 -0800 Subject: [PATCH 23/30] Use `2to3` automatically during the build process. It doesn't make a ton of changes in truth, but it's a recommended step on the conversion path. It does handle the `urllib` path though! --- README.rst | 4 ++++ embedly/client.py | 8 ++------ setup.py | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index b7dc81d..db3d206 100644 --- a/README.rst +++ b/README.rst @@ -18,6 +18,10 @@ Or setuptools:: git clone git://github.com/embedly/embedly-python.git python setup.py +Setup requires Setuptools 0.7+ or Distribute 0.6.2+ in order to take advantage +of the ``2to3`` option. Setup will still run on earlier versions but you'll +see a warning and ``2to3`` won't happen. Read more in the Setuptools +`docs `_ Getting Started --------------- diff --git a/embedly/client.py b/embedly/client.py index b78aeed..cfda2df 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -4,15 +4,11 @@ The embedly object that interacts with the service """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re import httplib2 import json - -try: # pragma: no cover - from urllib.parse import quote, urlencode # pragma: no cover -except ImportError: # Python 2 # pragma: no cover - from urllib import quote, urlencode # pragma: no cover +from urllib import quote, urlencode from .models import Url diff --git a/setup.py b/setup.py index acb908c..bf63710 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,9 @@ def get_version(): packages=['embedly'], install_requires=required, tests_require=tests_require, + test_suite="embedly.tests", zip_safe=True, + use_2to3=True, classifiers=( 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', From b9292c4a70fb4e770f922d4f6bf832985366abd1 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 21 Nov 2013 16:53:39 -0800 Subject: [PATCH 24/30] TravisCI - let's see if it will use `setup.py test` and thus install the test dependencies --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 718566f..2a16cc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,6 @@ python: - 2.7 - 3.2 - 3.3 -script: python embedly/tests.py -install: - - python setup.py -q install \ No newline at end of file +env: + - PIP_USE_MIRRORS=true +script: python setup.py test \ No newline at end of file From e692ed8f15196696be1a3a6ae80ff9f01049780e Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Thu, 21 Nov 2013 16:13:23 -0800 Subject: [PATCH 25/30] Python 3 - properly handle `__str__` vs `__unicode__` . Also test for it, which increases test coverage to 100%. --- embedly/models.py | 12 +++--------- embedly/py3_utils.py | 28 ++++++++++++++++++++++++++++ embedly/tests.py | 13 +++++++++++++ 3 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 embedly/py3_utils.py diff --git a/embedly/models.py b/embedly/models.py index d347ef1..f97591c 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -1,11 +1,8 @@ -from __future__ import unicode_literals - -try: # pragma: no cover - from collections import UserDict as IterableUserDict # pragma: no cover -except ImportError: # Python 2 # pragma: no cover - from UserDict import IterableUserDict # pragma: no cover +from __future__ import absolute_import, unicode_literals +from .py3_utils import python_2_unicode_compatible, IterableUserDict +@python_2_unicode_compatible class Url(IterableUserDict, object): """ A dictionary with two additional attributes for the method and url. @@ -19,7 +16,4 @@ def __init__(self, data=None, method=None, original_url=None, **kwargs): self.original_url = original_url def __str__(self): - return self.__unicode__().encode("utf-8") - - def __unicode__(self): return '<%s %s>' % (self.method.title(), self.original_url or "") diff --git a/embedly/py3_utils.py b/embedly/py3_utils.py new file mode 100644 index 0000000..089ce8e --- /dev/null +++ b/embedly/py3_utils.py @@ -0,0 +1,28 @@ +import sys + +# 2to3 doesn't handle the UserDict relocation +# put the import logic here for cleaner usage +try: + from collections import UserDict as IterableUserDict +except ImportError: # Python 2 + from UserDict import IterableUserDict + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + From django.utils.encoding.py in 1.4.2+, minus the dependency on Six. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if sys.version_info[0] == 2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass diff --git a/embedly/tests.py b/embedly/tests.py index 227988f..90ac6d9 100644 --- a/embedly/tests.py +++ b/embedly/tests.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals import re +import sys import json try: # pragma: no cover @@ -78,6 +79,18 @@ def test_model_data_can_serialize(self): unserialzed = json.loads(json.dumps(obj.data)) self.assertDictEqual(obj.data, unserialzed) + def test_str_representation(self): + unistr = 'I\xf1t\xebrn\xe2ti\xf4n\xe0liz\xe6tion' + url = "http://test.com" + obj = Url(method=unistr, original_url=url) + + if sys.version_info[0] == 2: + self.assertTrue(unistr.encode('utf-8') in str(obj)) + self.assertTrue(url.encode('utf-8') in str(obj)) + else: + self.assertTrue(unistr in str(obj)) + self.assertTrue(url in str(obj)) + class EmbedlyTestCase(unittest.TestCase): def setUp(self): From d32c2144d347b600174d09df98600ec4f226f4f8 Mon Sep 17 00:00:00 2001 From: Jon Cotton Date: Fri, 10 Jan 2014 12:51:32 -0800 Subject: [PATCH 26/30] Update .travis.yml Remove --use-mirrors for pip. The feature was removed in the newest pip 1.5. See https://github.com/pypa/pip/blob/c2361e72da2c31a3596d49dc5fccb7d2bdb8c032/CHANGES.txt#L7-L10 --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2a16cc6..8891f22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,4 @@ python: - 2.7 - 3.2 - 3.3 -env: - - PIP_USE_MIRRORS=true -script: python setup.py test \ No newline at end of file +script: python setup.py test From 314d74989411c1dd690cd004377f9108d0d1feb1 Mon Sep 17 00:00:00 2001 From: Bob Corsaro Date: Mon, 15 Dec 2014 12:35:24 -0500 Subject: [PATCH 27/30] Better documentation --- README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index db3d206..50416c7 100644 --- a/README.rst +++ b/README.rst @@ -76,6 +76,8 @@ The client object now has a bunch of different methods that you can use. ``preview`` + **Preview is no longer available to new users and has been replaced by extract.** + Corresponds to the `Preview endpoint `_. Passes back a simple object that allows you to retrieve a title, description, content, html and a list of @@ -85,6 +87,8 @@ The client object now has a bunch of different methods that you can use. ``objectify`` + **Objectify is no longer available to new users and has been replaced by extract.** + Corresponds to the `Objectify endpoint `_. Passes back a simple object that allows you to retrieve pretty much everything that Embedly knows about a @@ -185,4 +189,4 @@ an error. For example if we use an invalid key, we will get a 401 response back Copyright --------- -Copyright (c) 2013 Embed.ly, Inc. See LICENSE for details. \ No newline at end of file +Copyright (c) 2013 Embed.ly, Inc. See LICENSE for details. From 41fb4dec6795c03e01e3e701a78a92340fc6d274 Mon Sep 17 00:00:00 2001 From: alex-money Date: Sat, 24 Jun 2017 17:27:58 -0400 Subject: [PATCH 28/30] use https? I noticed that everything is "https://". Shouldn't we use "https://"? To hide our keys from public view? --- embedly/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embedly/client.py b/embedly/client.py index cfda2df..3aff738 100644 --- a/embedly/client.py +++ b/embedly/client.py @@ -52,7 +52,7 @@ def get_services(self): if self.services: return self.services - url = 'http://api.embed.ly/1/services/python' + url = 'https://api.embed.ly/1/services/python' http = httplib2.Http(timeout=self.timeout) headers = {'User-Agent': self.user_agent, @@ -122,7 +122,7 @@ def _get(self, version, method, url_or_urls, **kwargs): else: query += '&url=%s' % quote(url_or_urls) - url = 'http://api.embed.ly/%s/%s?%s' % (version, method, query) + url = 'https://api.embed.ly/%s/%s?%s' % (version, method, query) http = httplib2.Http(timeout=self.timeout) From a00c965beff040be648d68578f55a3b4fda0f9dd Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Thu, 25 Nov 2021 06:42:00 +1100 Subject: [PATCH 29/30] docs: Fix a few typos There are small typos in: - README.rst - embedly/models.py Fixes: - Should read `arguments` rather than `arguements`. - Should read `accessible` rather than `accsesible`. --- README.rst | 2 +- embedly/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 50416c7..f514ce2 100644 --- a/README.rst +++ b/README.rst @@ -97,7 +97,7 @@ The client object now has a bunch of different methods that you can use. >>> client.objectify('http://vimeo.com/18150336') -The above functions all take the same arguements, a URL or a list of URLs and +The above functions all take the same arguments, a URL or a list of URLs and keyword arguments that correspond to Embedly's `query arguments `_. Here is an example:: diff --git a/embedly/models.py b/embedly/models.py index f97591c..e3f7ad8 100644 --- a/embedly/models.py +++ b/embedly/models.py @@ -7,7 +7,7 @@ class Url(IterableUserDict, object): """ A dictionary with two additional attributes for the method and url. UserDict provides a dictionary interface along with the regular - dictionary accsesible via the `data` attribute. + dictionary accessible via the `data` attribute. """ def __init__(self, data=None, method=None, original_url=None, **kwargs): From 6be75a250a255e98f893a5a5f0202b6f449b3368 Mon Sep 17 00:00:00 2001 From: Shane Spencer <305301+whardier@users.noreply.github.com> Date: Thu, 17 Feb 2022 21:19:44 +0000 Subject: [PATCH 30/30] fix tuple/list error to resolve issue with newer setuptools --- embedly/__init__.py | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/embedly/__init__.py b/embedly/__init__.py index 5f9c9b4..11f965c 100644 --- a/embedly/__init__.py +++ b/embedly/__init__.py @@ -1,4 +1,4 @@ from __future__ import absolute_import from .client import Embedly -__version__ = '0.5.0' +__version__ = '0.5.0.post0' diff --git a/setup.py b/setup.py index bf63710..e0fef44 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ def get_version(): test_suite="embedly.tests", zip_safe=True, use_2to3=True, - classifiers=( + classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Natural Language :: English', @@ -54,5 +54,5 @@ def get_version(): 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', - ) + ] )