├── .gitignore ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── README.markdown ├── examples └── simple.py ├── oembed └── __init__.py ├── setup.cfg ├── setup.py └── test └── test_net.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.so 3 | *~ 4 | build 5 | dist/ 6 | python_oembed.egg-info 7 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | History 2 | ======= 3 | 4 | 0.2.4 (2016-01-01) 5 | ------------------ 6 | 7 | * Fix packaging por pypi 8 | 9 | 0.2.3 (2015-04-02) 10 | ------------------ 11 | 12 | * Add Python 3 compatibility 13 | 14 | 0.2.2 (2012-06-08) 15 | ------------------ 16 | 17 | * If the endpoint URL already had URL parameters, use an ampersand instead to append URL parameters. 18 | 19 | 0.2.1 (2011-12-29) 20 | ------------------ 21 | 22 | * Fix build problems in setup.py 23 | 24 | 0.2.0 (2011-10-20) 25 | ------------------ 26 | 27 | * Update code format 28 | * Improve import selection of JSON and XML libraries 29 | * Remove unused files 30 | 31 | 0.1.2 (2009-07-28) 32 | ------------------ 33 | 34 | * Bugfix: resolved issue #1 35 | * Added test case to the URL matching 36 | 37 | 0.1.1 (2008-09-08) 38 | ------------------ 39 | 40 | * Fixed mime-type check. It failed when more tokens where present in the content-type. 41 | * Added new mime-type "application/xml". 42 | * Changed minor error on package descriptor. 43 | 44 | 0.1.0 (2008-09-05) 45 | ------------------ 46 | 47 | * First release 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Ariel Barmat 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include HISTORY.rst 2 | include README.markdown 3 | include LICENSE 4 | recursive-include examples * 5 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Python-oembed 2 | ==== 3 | 4 | This library provides a pure python OEmbed consumer to get resources from OEmbed providers. 5 | 6 | Based on reference from http://oembed.com/ 7 | 8 | oEmbed is a format for allowing an embedded representation of a URL on third party sites. The simple API allows a website to display embedded content (such as photos or videos) when a user posts a link to that resource, without having to parse the resource directly. 9 | 10 | OEmbed format authors: 11 | 12 | * Cal Henderson (cal at iamcal.com) 13 | * Mike Malone (mjmalone at gmail.com) 14 | * Leah Culver (leah.culver at gmail.com) 15 | * Richard Crowley (r at rcrowley.org) 16 | 17 | Install 18 | ------------ 19 | 20 | pip install python-oembed 21 | 22 | Use 23 | ------------ 24 | 25 | To create an instance of the oEmbed consumer: 26 | 27 | >>> import oembed 28 | >>> consumer = oembed.OEmbedConsumer() 29 | 30 | To add a provider endpoint to this consumer: 31 | 32 | * The first parameter is the URL of the endpoint published by the provider. 33 | * The second parameter is the URL schema to use with this endpoint. 34 | 35 | For example: 36 | 37 | >>> endpoint = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed', ['http://*.flickr.com/*']) 38 | >>> consumer.addEndpoint(endpoint) 39 | 40 | To get the provider response for a URL: 41 | 42 | >>> response = consumer.embed('http://www.flickr.com/photos/wizardbt/2584979382/') 43 | 44 | To print the response results (each field can be read as a python dict, None if empty): 45 | 46 | >>> import pprint 47 | >>> pprint.pprint(response.getData()) 48 | >>> print response['url'] 49 | 50 | To read the full documentation: 51 | 52 | $ pydoc oembed 53 | 54 | Test 55 | ------------ 56 | 57 | cd python-oembed 58 | python oembed_test.py 59 | 60 | License 61 | ------------ 62 | Copyright (c) 2008 Ariel Barmat 63 | 64 | Permission is hereby granted, free of charge, to any person obtaining 65 | a copy of this software and associated documentation files (the 66 | "Software"), to deal in the Software without restriction, including 67 | without limitation the rights to use, copy, modify, merge, publish, 68 | distribute, sublicense, and/or sell copies of the Software, and to 69 | permit persons to whom the Software is furnished to do so, subject to 70 | the following conditions: 71 | 72 | The above copyright notice and this permission notice shall be 73 | included in all copies or substantial portions of the Software. 74 | 75 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 76 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 77 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 78 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 79 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 80 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 81 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE -------------------------------------------------------------------------------- /examples/simple.py: -------------------------------------------------------------------------------- 1 | import oembed 2 | 3 | consumer = oembed.OEmbedConsumer() 4 | endpoint = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed', \ 5 | ['http://*.flickr.com/*']) 6 | consumer.addEndpoint(endpoint) 7 | 8 | response = consumer.embed('http://www.flickr.com/photos/wizardbt/2584979382/') 9 | 10 | print response['url'] 11 | 12 | import pprint 13 | pprint.pprint(response.getData()) 14 | -------------------------------------------------------------------------------- /oembed/__init__.py: -------------------------------------------------------------------------------- 1 | '''A Python library that implements an OEmbed consumer to use with OEmbed providers. 2 | 3 | Based on reference from http://oembed.com/ 4 | 5 | oEmbed is a format for allowing an embedded representation of a URL on 6 | third party sites. The simple API allows a website to display embedded content 7 | (such as photos or videos) when a user posts a link to that resource, without 8 | having to parse the resource directly. 9 | 10 | OEmbed format authors: 11 | * Cal Henderson (cal [at] iamcal.com) 12 | * Mike Malone (mjmalone [at] gmail.com) 13 | * Leah Culver (leah.culver [at] gmail.com) 14 | * Richard Crowley (r [at] rcrowley.org) 15 | 16 | 17 | Simple usage: 18 | 19 | import oembed 20 | 21 | consumer = oembed.OEmbedConsumer() 22 | endpoint = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed', 23 | ['http://*.flickr.com/*']) 24 | consumer.addEndpoint(endpoint) 25 | 26 | response = consumer.embed('http://www.flickr.com/photos/wizardbt/2584979382/') 27 | 28 | print response['url'] 29 | 30 | import pprint 31 | pprint.pprint(response.getData()) 32 | 33 | 34 | Copyright (c) 2008 Ariel Barmat, abarmat@gmail.com 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining 37 | a copy of this software and associated documentation files (the 38 | "Software"), to deal in the Software without restriction, including 39 | without limitation the rights to use, copy, modify, merge, publish, 40 | distribute, sublicense, and/or sell copies of the Software, and to 41 | permit persons to whom the Software is furnished to do so, subject to 42 | the following conditions: 43 | 44 | The above copyright notice and this permission notice shall be 45 | included in all copies or substantial portions of the Software. 46 | 47 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 48 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 49 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 50 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 51 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 52 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 53 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE 54 | 55 | ''' 56 | try: 57 | import urllib.parse as urllib # Python 3 58 | except ImportError: 59 | import urllib # Python 2 60 | 61 | try: 62 | import urllib.request as urllib2 # Python 3 63 | except ImportError: 64 | import urllib2 # Python 2 65 | 66 | import re 67 | 68 | # json module is in the standard library as of python 2.6; fall back to 69 | # simplejson if present for older versions. 70 | try: 71 | import json 72 | assert hasattr(json, "loads") and hasattr(json, "dumps") 73 | json_decode = json.loads 74 | json_encode = json.dumps 75 | except Exception: 76 | try: 77 | import simplejson 78 | json_decode = lambda s: simplejson.loads(_unicode(s)) 79 | json_encode = lambda v: simplejson.dumps(v) 80 | except ImportError: 81 | try: 82 | # For Google AppEngine 83 | from django.utils import simplejson 84 | json_decode = lambda s: simplejson.loads(_unicode(s)) 85 | json_encode = lambda v: simplejson.dumps(v) 86 | except ImportError: 87 | def json_decode(s): 88 | raise NotImplementedError( 89 | "A JSON parser is required, e.g., simplejson at " 90 | "http://pypi.python.org/pypi/simplejson/") 91 | json_encode = json_decode 92 | 93 | try: 94 | from xml.etree import cElementTree as etree 95 | except ImportError: 96 | # Running Python < 2.4 so we need a different import 97 | import cElementTree as etree 98 | 99 | 100 | class OEmbedError(Exception): 101 | '''Base class for OEmbed errors''' 102 | 103 | class OEmbedInvalidRequest(OEmbedError): 104 | '''Raised when an invalid parameter is used in a request''' 105 | 106 | class OEmbedNoEndpoint(OEmbedError): 107 | '''Raised when no endpoint is available for a particular URL''' 108 | 109 | 110 | class OEmbedResponse(object): 111 | ''' 112 | Base class for all OEmbed responses. 113 | 114 | This class provides a factory of OEmbed responses according to the format 115 | detected in the type field. It also validates that mandatory fields are 116 | present. 117 | ''' 118 | def _validateData(self, data): 119 | pass 120 | 121 | def __getitem__(self, name): 122 | return self._data.get(name) 123 | 124 | def getData(self): 125 | return self._data 126 | 127 | def loadData(self, data): 128 | self._validateData(data) 129 | self._data = data 130 | 131 | @classmethod 132 | def createLoad(cls, data): 133 | if not 'type' in data or \ 134 | not 'version' in data: 135 | raise OEmbedError('Missing required fields on OEmbed response.') 136 | response = cls.create(data['type']) 137 | response.loadData(data) 138 | return response 139 | 140 | @classmethod 141 | def create(cls, responseType): 142 | return resourceTypes.get(responseType, OEmbedResponse)() 143 | 144 | @classmethod 145 | def newFromJSON(cls, raw): 146 | data = json_decode(raw) 147 | return cls.createLoad(data) 148 | 149 | @classmethod 150 | def newFromXML(cls, raw): 151 | elem = etree.XML(raw) 152 | data = dict([(e.tag, e.text) for e in elem.getiterator() \ 153 | if e.tag not in ['oembed']]) 154 | return cls.createLoad(data) 155 | 156 | 157 | class OEmbedPhotoResponse(OEmbedResponse): 158 | ''' 159 | This type is used for representing static photos. 160 | ''' 161 | def _validateData(self, data): 162 | OEmbedResponse._validateData(self, data) 163 | 164 | if not 'url' in data or \ 165 | not 'width' in data or \ 166 | not 'height' in data: 167 | raise OEmbedError('Missing required fields on OEmbed photo response.') 168 | 169 | class OEmbedVideoResponse(OEmbedResponse): 170 | ''' 171 | This type is used for representing playable videos. 172 | ''' 173 | def _validateData(self, data): 174 | OEmbedResponse._validateData(self, data) 175 | 176 | if not 'html' in data or \ 177 | not 'width' in data or \ 178 | not 'height' in data: 179 | raise OEmbedError('Missing required fields on OEmbed video response.') 180 | 181 | class OEmbedLinkResponse(OEmbedResponse): 182 | ''' 183 | Responses of this type allow a provider to return any generic embed data 184 | (such as title and author_name), without providing either the url or html 185 | parameters. The consumer may then link to the resource, using the URL 186 | specified in the original request. 187 | ''' 188 | 189 | class OEmbedRichResponse(OEmbedResponse): 190 | ''' 191 | This type is used for rich HTML content that does not fall under 192 | one of the other categories. 193 | ''' 194 | def _validateData(self, data): 195 | OEmbedResponse._validateData(self, data) 196 | 197 | if not 'html' in data or \ 198 | not 'width' in data or \ 199 | not 'height' in data: 200 | raise OEmbedError('Missing required fields on OEmbed rich response.') 201 | 202 | 203 | resourceTypes = { 204 | 'photo': OEmbedPhotoResponse, 205 | 'video': OEmbedVideoResponse, 206 | 'link': OEmbedLinkResponse, 207 | 'rich': OEmbedRichResponse 208 | } 209 | 210 | class OEmbedEndpoint(object): 211 | ''' 212 | A class representing an OEmbed Endpoint exposed by a provider. 213 | 214 | This class handles a number of URL schemes and manage resource retrieval. 215 | ''' 216 | 217 | def __init__(self, url, urlSchemes=None): 218 | ''' 219 | Create a new OEmbedEndpoint object. 220 | 221 | Args: 222 | url: The url of a provider API (API endpoint). 223 | urlSchemes: A list of URL schemes for this endpoint. 224 | ''' 225 | self._urlApi = url 226 | self._urlSchemes = {} 227 | self._initRequestHeaders() 228 | self._urllib = urllib2 229 | 230 | if urlSchemes is not None: 231 | for urlScheme in urlSchemes: 232 | self.addUrlScheme(urlScheme) 233 | 234 | self._implicitFormat = self._urlApi.find('{format}') != -1 235 | 236 | def _initRequestHeaders(self): 237 | self._requestHeaders = {} 238 | self.setUserAgent('python-oembed') 239 | 240 | def addUrlScheme(self, url): 241 | ''' 242 | Add a url scheme to this endpoint. It takes a url string and create 243 | the OEmbedUrlScheme object internally. 244 | 245 | Args: 246 | url: The url string that represents a url scheme to add. 247 | ''' 248 | 249 | #@TODO: validate invalid url format according to http://oembed.com/ 250 | if not isinstance(url, str): 251 | raise TypeError('url must be a string value') 252 | 253 | if not url in self._urlSchemes: 254 | self._urlSchemes[url] = OEmbedUrlScheme(url) 255 | 256 | def delUrlScheme(self, url): 257 | ''' 258 | Remove an OEmbedUrlScheme from the list of schemes. 259 | 260 | Args: 261 | url: The url used as key for the urlSchems dict. 262 | ''' 263 | if url in self._urlSchemes: 264 | del self._urlSchemes[url] 265 | 266 | def clearUrlSchemes(self): 267 | '''Clear the schemes in this endpoint.''' 268 | self._urlSchemes.clear() 269 | 270 | def getUrlSchemes(self): 271 | ''' 272 | Get the url schemes in this endpoint. 273 | 274 | Returns: 275 | A dict of OEmbedUrlScheme objects. k => url, v => OEmbedUrlScheme 276 | ''' 277 | return self._urlSchemes 278 | 279 | def match(self, url): 280 | ''' 281 | Try to find if url matches against any of the schemes within this 282 | endpoint. 283 | 284 | Args: 285 | url: The url to match against each scheme 286 | 287 | Returns: 288 | True if a matching scheme was found for the url, False otherwise 289 | ''' 290 | try: 291 | urlSchemes = self._urlSchemes.itervalues() # Python 2 292 | except AttributeError: 293 | urlSchemes = self._urlSchemes.values() # Python 3 294 | 295 | for urlScheme in urlSchemes: 296 | if urlScheme.match(url): 297 | return True 298 | return False 299 | 300 | def request(self, url, **opt): 301 | ''' 302 | Format the input url and optional parameters, and provides the final url 303 | where to get the given resource. 304 | 305 | Args: 306 | url: The url of an OEmbed resource. 307 | **opt: Parameters passed to the url. 308 | 309 | Returns: 310 | The complete url of the endpoint and resource. 311 | ''' 312 | params = opt 313 | params['url'] = url 314 | urlApi = self._urlApi 315 | 316 | if 'format' in params and self._implicitFormat: 317 | urlApi = self._urlApi.replace('{format}', params['format']) 318 | del params['format'] 319 | 320 | if '?' in urlApi: 321 | return "%s&%s" % (urlApi, urllib.urlencode(params)) 322 | else: 323 | return "%s?%s" % (urlApi, urllib.urlencode(params)) 324 | 325 | def get(self, url, **opt): 326 | ''' 327 | Convert the resource url to a complete url and then fetch the 328 | data from it. 329 | 330 | Args: 331 | url: The url of an OEmbed resource. 332 | **opt: Parameters passed to the url. 333 | 334 | Returns: 335 | OEmbedResponse object according to data fetched 336 | ''' 337 | return self.fetch(self.request(url, **opt)) 338 | 339 | def fetch(self, url): 340 | ''' 341 | Fetch url and create a response object according to the mime-type. 342 | 343 | Args: 344 | url: The url to fetch data from 345 | 346 | Returns: 347 | OEmbedResponse object according to data fetched 348 | ''' 349 | opener = self._urllib.build_opener() 350 | opener.addheaders = self._requestHeaders.items() 351 | response = opener.open(url) 352 | headers = response.info() 353 | raw = response.read() 354 | raw = raw.decode('utf8') 355 | 356 | if not 'Content-Type' in headers: 357 | raise OEmbedError('Missing mime-type in response') 358 | 359 | if headers['Content-Type'].find('application/xml') != -1 or \ 360 | headers['Content-Type'].find('text/xml') != -1: 361 | response = OEmbedResponse.newFromXML(raw) 362 | elif headers['Content-Type'].find('application/json') != -1 or \ 363 | headers['Content-Type'].find('text/javascript') != -1 or \ 364 | headers['Content-Type'].find('text/json') != -1: 365 | response = OEmbedResponse.newFromJSON(raw) 366 | else: 367 | raise OEmbedError('Invalid mime-type in response - %s' % headers['Content-Type']) 368 | 369 | return response 370 | 371 | def setUrllib(self, urllib): 372 | ''' 373 | Override the default urllib implementation. 374 | 375 | Args: 376 | urllib: an instance that supports the same API as the urllib2 module 377 | ''' 378 | self._urllib = urllib 379 | 380 | def setUserAgent(self, user_agent): 381 | ''' 382 | Override the default user agent 383 | 384 | Args: 385 | user_agent: a string that should be send to the server as the User-agent 386 | ''' 387 | self._requestHeaders['User-Agent'] = user_agent 388 | 389 | 390 | class OEmbedUrlScheme(object): 391 | ''' 392 | A class representing an OEmbed URL schema. 393 | ''' 394 | 395 | def __init__(self, url): 396 | ''' 397 | Create a new OEmbedUrlScheme instance. 398 | 399 | Args; 400 | url: The url scheme. It also takes the wildcard character (*). 401 | ''' 402 | self._url = url 403 | if url.startswith('regex:'): 404 | self._regex = re.compile(url[6:]) 405 | else: 406 | self._regex = re.compile(url.replace('.', '\.')\ 407 | .replace('*', '.*')) 408 | 409 | def getUrl(self): 410 | ''' 411 | Get the url scheme. 412 | 413 | Returns: 414 | The url scheme. 415 | ''' 416 | return self._url 417 | 418 | def match(self, url): 419 | ''' 420 | Match the url against this scheme. 421 | 422 | Args: 423 | url: The url to match against this scheme. 424 | 425 | Returns: 426 | True if a match was found for the url, False otherwise 427 | ''' 428 | return self._regex.match(url) is not None 429 | 430 | def __repr__(self): 431 | return "%s - %s" % (object.__repr__(self), self._url) 432 | 433 | 434 | class OEmbedConsumer(object): 435 | ''' 436 | A class representing an OEmbed consumer. 437 | 438 | This class manages a number of endpoints, selects the corresponding one 439 | according to the resource url passed to the embed function and fetches 440 | the data. 441 | ''' 442 | def __init__(self): 443 | self._endpoints = [] 444 | 445 | def addEndpoint(self, endpoint): 446 | ''' 447 | Add a new OEmbedEndpoint to be manage by the consumer. 448 | 449 | Args: 450 | endpoint: An instance of an OEmbedEndpoint class. 451 | ''' 452 | self._endpoints.append(endpoint) 453 | 454 | def delEndpoint(self, endpoint): 455 | ''' 456 | Remove an OEmbedEnpoint from this consumer. 457 | 458 | Args: 459 | endpoint: An instance of an OEmbedEndpoint class. 460 | ''' 461 | self._endpoints.remove(endpoint) 462 | 463 | def clearEndpoints(self): 464 | '''Clear all the endpoints managed by this consumer.''' 465 | del self._endpoints[:] 466 | 467 | def getEndpoints(self): 468 | ''' 469 | Get the list of endpoints. 470 | 471 | Returns: 472 | The list of endpoints in this consumer. 473 | ''' 474 | return self._endpoints 475 | 476 | def _endpointFor(self, url): 477 | for endpoint in self._endpoints: 478 | if endpoint.match(url): 479 | return endpoint 480 | return None 481 | 482 | def _request(self, url, **opt): 483 | endpoint = self._endpointFor(url) 484 | if endpoint is None: 485 | raise OEmbedNoEndpoint('There are no endpoints available for %s' % url) 486 | return endpoint.get(url, **opt) 487 | 488 | def embed(self, url, format='json', **opt): 489 | ''' 490 | Get an OEmbedResponse from one of the providers configured in this 491 | consumer according to the resource url. 492 | 493 | Args: 494 | url: The url of the resource to get. 495 | format: Desired response format. 496 | **opt: Optional parameters to pass in the url to the provider. 497 | 498 | Returns: 499 | OEmbedResponse object. 500 | ''' 501 | if format not in ['json', 'xml']: 502 | raise OEmbedInvalidRequest('Format must be json or xml') 503 | opt['format'] = format 504 | return self._request(url, **opt) 505 | 506 | def _unicode(value): 507 | if isinstance(value, str): 508 | return value.decode("utf-8") 509 | assert isinstance(value, unicode) 510 | return value 511 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | tag_date = 0 4 | tag_svn_revision = 0 5 | 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | try: 7 | from setuptools import setup 8 | except ImportError: 9 | from distutils.core import setup 10 | 11 | def publish(): 12 | """Publish to Pypi""" 13 | os.system("python setup.py sdist upload") 14 | 15 | if sys.argv[-1] == "publish": 16 | publish() 17 | sys.exit() 18 | 19 | version = '0.2.4' 20 | 21 | setup( 22 | name='python-oembed', 23 | version=version, 24 | description='OEmbed consumer library for Python', 25 | long_description=open('README.markdown').read() + '\n\n' + open('HISTORY.rst').read(), 26 | author='Ariel Barmat', 27 | author_email='abarmat@gmail.com', 28 | url='http://github.com/abarmat/python-oembed', 29 | packages=['oembed'], 30 | license='MIT', 31 | classifiers = ( 32 | "Development Status :: 4 - Beta", 33 | "License :: OSI Approved :: MIT License", 34 | "Programming Language :: Python", 35 | "Programming Language :: Python :: 2.5", 36 | "Programming Language :: Python :: 2.6", 37 | "Programming Language :: Python :: 3.4", 38 | ), 39 | keywords='oembed python api' 40 | ) 41 | -------------------------------------------------------------------------------- /test/test_net.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import oembed 3 | 4 | try: 5 | import urllib.request as urllib2 # Python 3 6 | except ImportError: 7 | import urllib2 # Python 2 8 | 9 | 10 | class EndpointTest(unittest.TestCase): 11 | def testInit(self): 12 | #plain init 13 | ep = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed') 14 | self.assertEqual(len(ep.getUrlSchemes()), 0) 15 | 16 | #init with schemes 17 | ep = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed',\ 18 | ['http://*.flickr.com/*',\ 19 | 'http://flickr.com/*']) 20 | self.assertEqual(len(ep.getUrlSchemes()), 2) 21 | 22 | def testUrlScheme(self): 23 | ep = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed') 24 | 25 | #add some schemes 26 | ep.addUrlScheme('http://flickr.com/*') 27 | ep.addUrlScheme('http://*.flickr.com/*') 28 | self.assertEqual(len(ep.getUrlSchemes()), 2) 29 | 30 | #add duplicated 31 | ep.addUrlScheme('http://*.flickr.com/*') 32 | self.assertEqual(len(ep.getUrlSchemes()), 2) 33 | 34 | #remove url 35 | ep.addUrlScheme('http://*.flickr.com/') 36 | ep.delUrlScheme('http://flickr.com/*') 37 | self.assertEqual(len(ep.getUrlSchemes()), 2) 38 | 39 | #clear all 40 | ep.clearUrlSchemes() 41 | self.assertEqual(len(ep.getUrlSchemes()), 0) 42 | 43 | class UrlSchemeTest(unittest.TestCase): 44 | def testInit(self): 45 | scheme = oembed.OEmbedUrlScheme('http://*.flickr.com/*') 46 | 47 | self.assertEqual(scheme.getUrl(), 'http://*.flickr.com/*') 48 | 49 | self.assertTrue(scheme.match('http://www.flickr.com/photos/wizardbt/2584979382/')) 50 | self.assertFalse(scheme.match('http://flickr.com/photos/wizardbt/2584979382/')) 51 | self.assertFalse(scheme.match('http://flick.com/photos/wizardbt/2584979382/')) 52 | self.assertFalse(scheme.match('https://flickr.com/photos/wizardbt/2584979382/')) 53 | self.assertFalse(scheme.match('flickr.com/photos/wizardbt/2584979382/')) 54 | self.assertFalse(scheme.match('http://flickr/photos/wizardbt/2584979382/')) 55 | self.assertFalse(scheme.match('http://conflickr.com/')) 56 | 57 | class ConsumerTest(unittest.TestCase): 58 | def testGettersAndSetters(self): 59 | consumer = oembed.OEmbedConsumer() 60 | 61 | ep1 = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed') 62 | ep2 = oembed.OEmbedEndpoint('http://api.pownce.com/2.1/oembed.{format}') 63 | ep3 = oembed.OEmbedEndpoint('http://www.vimeo.com/api/oembed.{format}') 64 | 65 | #adding 66 | consumer.addEndpoint(ep1) 67 | consumer.addEndpoint(ep2) 68 | consumer.addEndpoint(ep3) 69 | self.assertEqual(len(consumer.getEndpoints()), 3) 70 | 71 | #removing one 72 | consumer.delEndpoint(ep2) 73 | self.assertEqual(len(consumer.getEndpoints()), 2) 74 | 75 | #clearing all! 76 | consumer.clearEndpoints() 77 | self.assertEqual(len(consumer.getEndpoints()), 0) 78 | 79 | def testEmbed(self): 80 | consumer = oembed.OEmbedConsumer() 81 | 82 | ep1 = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed') 83 | ep1.addUrlScheme('http://*.flickr.com/*') 84 | 85 | ep2 = oembed.OEmbedEndpoint('http://api.pownce.com/2.1/oembed.{format}') 86 | ep2.addUrlScheme('http://*.pownce.com/*') 87 | 88 | consumer.addEndpoint(ep1) 89 | consumer.addEndpoint(ep2) 90 | 91 | #invalid format 92 | self.assertRaises(oembed.OEmbedInvalidRequest, consumer.embed, \ 93 | 'http://www.flickr.com/photos/wizardbt/2584979382/', \ 94 | format='text') 95 | 96 | #no matching endpoint for the url 97 | self.assertRaises(oembed.OEmbedNoEndpoint, consumer.embed, \ 98 | 'http://google.com/123456') 99 | 100 | def testResponses(self): 101 | consumer = oembed.OEmbedConsumer() 102 | 103 | ep = oembed.OEmbedEndpoint('http://www.flickr.com/services/oembed') 104 | ep.addUrlScheme('http://*.flickr.com/*') 105 | 106 | consumer.addEndpoint(ep) 107 | 108 | #json 109 | resp = consumer.embed('http://www.flickr.com/photos/wizardbt/2584979382/', 110 | format='json') 111 | #xml 112 | resp = consumer.embed('http://www.flickr.com/photos/wizardbt/2584979382/', 113 | format='xml') 114 | 115 | #resource not found 116 | self.assertRaises(urllib2.HTTPError, consumer.embed, \ 117 | 'http://www.flickr.com/photos/wizardbt/', \ 118 | format='json') 119 | 120 | def testNoEndpoints(self): 121 | consumer = oembed.OEmbedConsumer() 122 | 123 | self.assertRaises(oembed.OEmbedNoEndpoint, consumer.embed, \ 124 | 'http://www.flickr.com/photos/wizardbt/2584979382/') 125 | 126 | def testBrokenEndpoint(self): 127 | consumer = oembed.OEmbedConsumer() 128 | 129 | ep = oembed.OEmbedEndpoint('http://localhost') 130 | ep.addUrlScheme('http://localhost/*') 131 | consumer.addEndpoint(ep) 132 | 133 | self.assertRaises(urllib2.URLError, consumer.embed, \ 134 | 'http://localhost/test') 135 | 136 | 137 | def suite(): 138 | suite = unittest.TestSuite() 139 | suite.addTests(unittest.makeSuite(EndpointTest)) 140 | suite.addTests(unittest.makeSuite(UrlSchemeTest)) 141 | suite.addTests(unittest.makeSuite(ConsumerTest)) 142 | return suite 143 | 144 | 145 | if __name__ == '__main__': 146 | unittest.main() 147 | --------------------------------------------------------------------------------