Metadata-Version: 1.0
Name: ao.shorturl
Version: 1.1.7
Summary: Reusable url shortener and lookup library.
Home-page: http://github.com/aatiis/ao.shorturl
Author: Attila Olah
Author-email: attilaolah@gmail.com
License: GNU GPL
Description: In a nutshell
        =============
        
        ``ao.shorturl`` is a library for integrating short URLs to a web application.
        Its front-end configuration is not specific to any web application framework,
        instead it uses various back-ends for different frameworks.
        
        For example, ``ao.shorturl.appengine`` implements a Datastore backand for
        Google / Typhoon App Engine. If installed as a Django application,
        ``ao.shorturl`` also provides a template tag for easily displaying short URLs
        for any object that supports them.
        
        
        Using the short URL library without any framework
        =================================================
        
        
        Registering and getting handlers
        --------------------------------
        
        To use the library, you need to register a *handler* first, using the
        ``ao.shorturl.registerHandler()`` function. To get back the handler, use the
        ``getHandler()`` function::
        
        >>> from ao import shorturl
        >>> shorturl.getHandler()
        Traceback (most recent call last):
        ...
        ImproperlyConfigured: The requested handler is not initialized.
        
        >>> handler = shorturl.registerHandler()
        >>> shorturl.getHandler() is handler
        True
        >>> handler
        <ao.shorturl.BaseShortUrlHandler object at ...>
        
        Note that if you intend to use multiple handlers, you need to give them
        *names*, as the default handler is stored as a module global. However, to
        utilize named handlers, you need to make the ``zope.component`` and
        ``zope.interface`` packages available. Each handler is stored in the local
        site, meaning that if you use multiple sites, you can have different handlers
        with the same name on a per-site basis. However, the unnamed handler is still
        a *module* *global*, so take thet in consideration when using multiple
        handlers and sites::
        
        >>> foo = shorturl.registerHandler(name='foo')
        >>> shorturl.getHandler(name='foo') is shorturl.getHandler('foo') is foo
        True
        
        If you don't have the ``zope.component`` and ``zope.interface`` packages
        available, you won't be able to use named handlers.
        
        Let's pretend we don't have ``zope.component`` and ``zope.interface``::
        
        >>> import sys
        
        >>> class _():
        ...     def __init__(self, modules):
        ...         self.modules = modules
        ...
        ...     def find_module(self, fullname, path=None):
        ...         if fullname in self.modules:
        ...             raise ImportError('Debug import failure for %s' % fullname)
        ...
        
        >>> fail_loader = _(['zope.component', 'zope.interface'])
        >>> sys.meta_path.append(fail_loader)
        
        >>> for elem in ('zope.component', 'zope.interface'):
        ...     del sys.modules[elem]
        ...
        
        >>> reload(shorturl)
        <module 'ao.shorturl' from '...'>
        
        >>> del shorturl.zc  # delete the leftover zope.component module
        
        >>> shorturl.registerHandler(name='bar')
        Traceback (most recent call last):
        ...
        ImproperlyConfigured: To use named handlers, you need to make the ...
        
        >>> shorturl.getHandler('bar')
        Traceback (most recent call last):
        ...
        ImproperlyConfigured: To use named handlers, you need to make the ...
        
        Remove our import hook::
        
        >>> del sys.meta_path[0]
        
        
        Configuring the handler
        -----------------------
        
        To overwrite any default handler configuration, just pass the apropriate
        keyword argument to the ``ao.shorturl.registerHandler()`` function::
        
        >>> len(shorturl.registerHandler().generate_url())
        6
        
        >>> len(shorturl.registerHandler(url_length=10).generate_url())
        10
        
        >>> shorturl.registerHandler(url_length=10, url_elems='x').generate_url()
        'xxxxxxxxxx'
        
        
        Using custom handlers
        ---------------------
        
        When calling ``ao.shorturl.registerHandler()`` without a ``handler`` argument,
        it will not have any real functionality::
        
        >>> shorturl.registerHandler().assign_url(None)
        Traceback (most recent call last):
        ...
        NotImplementedError: You must overload `assign_url`.
        
        >>> shorturl.registerHandler().construct_url(None)
        Traceback (most recent call last):
        ...
        NotImplementedError: You must overload `construct_url`.
        
        Registering a custom handler is easy, just subclass
        ``ao.shorturl.BaseShortUrlHandler``::
        
        >>> class FancyShortUrlHandler(shorturl.BaseShortUrlHandler):
        ...     def assign_url(self, context):
        ...         context['shorturl'] = self.generate_url()
        ...     def get_context_from_cache(self, url):
        ...         if context['shorturl'] == url:
        ...             return context
        ...         raise LookupError
        ...
        >>> handler = shorturl.registerHandler(handler=FancyShortUrlHandler, url_length=20)
        >>> handler
        <FancyShortUrlHandler object at ...>
        
        >>> context = {'foo': 'bar'}
        >>> handler.assign_url(context)
        >>> len(context['shorturl']) == 20
        True
        
        As for now, there's one custom handler provided for App Engine:
        ``ao.shorturl.appengine.AppEngineShortUrlHandler``. It uses the datastore API
        to store the short url associations and the memcache API to cache the keys for
        better performance.
        
        
        Getting the context from the handler
        ------------------------------------
        
        In your view (if you're using an MCV framework), you can call the handler's
        ``get_context()`` method to query the context for a given short url::
        
        >>> handler.get_context('xxx')
        Traceback (most recent call last):
        ...
        ShortUrlNotFound: Short URL could not be found: xxx
        
        >>> handler.get_context(context['shorturl']) is context
        True
        
        Note that ``ao.shorturl.get_context()`` will be called at least once each time a
        new short url is created, to check for duplicates::
        
        >>> fired = False
        >>> def get_context(name):
        ...     global fired
        ...     if not fired:
        ...         print 'This URL already exists!'
        ...         fired = True
        ...         return 'Dummy context'
        ...     raise LookupError
        ...
        
        >>> handler.get_context = get_context
        
        >>> handler.generate_url()
        This URL already exists!
        '...'
        
        Clean up after the tests::
        
        >>> from zope.testing import cleanup
        >>> cleanup.cleanUp()
        
        
        Using with Django and template tags
        -----------------------------------
        
        If you use Django, you can access an object's short URL from a template with
        the ``shorturl`` template tag. To use it, add ``ao.shorturl`` to your
        ``INSTALLED_APPS``. Then in the template you can do something like this::
        
        {% load shorturl %}
        <a href="{% shorturl city %}">{{ city.name }}</a>
        
        Note that this will create an *absolute* url.
        
        Test the template tag::
        
        >>> from ao.shorturl.templatetags import shorturl
        
        >>> class Parser(object):
        ...     def split_contents(self):
        ...         return (None, 'xxx')
        ...
        
        >>> node = shorturl.shorturl(None, Parser())
        >>> node
        <ao.shorturl.templatetags.shorturl.URL object at ...>
        
        >>> node.render({'xxx': None})
        Traceback (most recent call last):
        ...
        NotImplementedError: You must overload `construct_url`.
        
        Clean up after the tests::
        
        >>> from zope.testing import cleanup
        >>> cleanup.cleanUp()
        
        
        Using the short URL library with App Engine
        ===========================================
        
        First set up a fake App Engine environment::
        
        >>> import minimock
        >>> import sys
        
        >>> mocks = (
        ...     'google',
        ...     'google.appengine',
        ...     'google.appengine.api',
        ...     'google.appengine.ext',
        ... )
        
        >>> sys.modules.update(dict((mock, minimock.Mock(mock)) for mock in mocks))
        
        >>> import ao.shorturl
        >>> import ao.shorturl.appengine
        Called google.appengine.ext.db.ReferenceProperty(...)
        
        To use the App Engine backend, simply import it and pass it as the ``handler``
        keyword argument to ``ao.shorturl.registerHandler()``::
        
        >>> handler = ao.shorturl.registerHandler(
        ...     handler=ao.shorturl.appengine.AppEngineShortUrlHandler,
        ... )
        
        >>> handler
        <ao.shorturl.appengine.AppEngineShortUrlHandler object at ...>
        
        Cache context will use `google.appengine.api.memcache`::
        
        >>> context = minimock.Mock('context')
        >>> handler.cache_context('someurl', context)
        Called context.key()
        Called google.appengine.api.memcache.add('someurl', 'None', 1200)
        
        1200 is the default value for the cache timeout, but you can overwrite it by
        passing the ``url_cache_time`` parameter to ``ao.shorturl.registerHandler()``.
        
        Test the cache return value::
        
        >>> from google.appengine.api import memcache
        >>> memcache.get.mock_returns = 'result'
        
        >>> handler.get_context_from_cache('someurl')
        Called google.appengine.api.memcache.get('someurl')
        Called google.appengine.ext.db.get('result')
        
        On failure it raises a ``LookupError``::
        
        >>> memcache.get.mock_returns = None
        
        >>> handler.get_context_from_cache('someurl')
        Traceback (most recent call last):
        ...
        LookupError: Context key not found in the cache.
        
        Same is true for the datastore storage backend. Let's fake the datastore to
        return a context for any key::
        
        >>> class FakeMapping(object):
        ...     context = 'context'
        ...
        
        >>> ao.shorturl.appengine.ShortUrl.get_by_key_name.mock_returns = \
        ...     FakeMapping()
        >>> handler.get_context_from_db('someurl')
        Called ShortUrl.get_by_key_name('someurl')
        'context'
        
        Otherwise it raises a ``LookupError``::
        
        >>> ao.shorturl.appengine.ShortUrl.get_by_key_name.mock_returns = None
        
        >>> handler.get_context_from_db('someurl')
        Traceback (most recent call last):
        ...
        LookupError: Context not found in the datastore.
        
        Try to construct a URL::
        
        >>> ao.shorturl.appengine.ShortUrl.mock_returns = minimock.Mock('shorturl')
        
        >>> context = minimock.Mock('context')
        >>> context.shorturl.count.mock_returns = 0
        
        >>> handler.construct_url(context)
        Traceback (most recent call last):
        ...
        TypeError: 'Mock' object is unindexable
        
        Clean up after the tests::
        
        >>> from zope.testing import cleanup
        >>> cleanup.cleanUp()
        
        
        TODO
        ====
        
        * Add backends for Django Models and SQLAlchemy/Elixir
        
        
        Changelog
        =========
        
        
        1.1.7 (2010-03-22)
        ==================
        
        * More documentation
        * 100% test coverage
        
        1.1.6 (2010-03-21)
        ==================
        
        * Fixed a few typos
        * More documentation
        * Better test coverage
        
        
        1.1.4 (2010-03-19)
        ==================
        
        * Added some unit tests
        * Updated the documentation
        * Changed the way registering and getting handlers work
        
        
        1.0.0 (2010-03-19)
        ==================
        
        * First public release
        * Added Django template tag
        * Added App Engine backend
        
        
        
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: Buildout
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.5
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Topic :: Internet
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
