Author: Sybren Stüvel

# Python FlickrAPI

Version: | 1.4
Author: | Sybren Stüvel

Contents

# 1 Introduction

Flickr is one of the most popular photo sharing websites. Their public API makes it very easy to write applications that use Flickr some way or another. The possibilities are limitless. This document describes how to use the Flickr API in your Python programs using the Python Flickr API interface.

This documentation does not specify what each Flickr API function does, nor what it returns. The Flickr API documentation is the source for that information, and will most likely be more up-to-date than this document could be. Since the Python Flickr API uses dynamic methods and introspection, you can call new Flickr methods as soon as they become available.

## 1.1 Concepts

To keep things simple, we do not write "he/she" or "(s)he". We know that men and women can all be fine programmers and end users. Some people will be addressed as male, others as female.

To be able to easily talk about Flickr, its users, programmers and applications, here is an explanation of some concepts we use.

you
  The reader of this document. We assume you are a programmer and that you are using this Python Flickr API to create an application. In this document we shall address you as male.
application
  The Python application you are creating, that has to interface with Flickr.
user
  The user of the application, and thus (either directly or indirectly via your application) a Flickr user. In this document we shall address the user as female.

# 2 Calling API functions

You start by creating a FlickrAPI object with your API key. This key can be obtained at Flickr Services. Once you have that key, the cool stuff can begin. Calling a Flickr function is very easy. Here are some examples:

    import flickrapi

    api_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

    flickr = flickrapi.FlickrAPI(api_key)
    photos = flickr.photos_search(user_id='73509078@N00', per_page='10')
    sets = flickr.photosets_getList(user_id='73509078@N00')

There is a simple naming scheme here. If the flickr function is called `flickr.photosets.getList` just call `photosets_getList` on your `flickr` object. In other words: replace the dots with underscores.

## 2.1 Parsing the return value

Flickr sends back XML when you call a function. This XML is parsed and returned to you. There are two parsers available: ElementTree and XMLNode. ElementTree was introduced in version 1.1, and replaced XMLNode as the default parser as of version 1.2.

In the following sections, we'll use a `sets = flickr.photosets_getList(...)` call and assume this was the response XML:

    <rsp stat='ok'>
        <photosets cancreate="1">
            <photoset id="5" primary="2483" secret="abcdef"
                    server="8" photos="4">
                <title>Test</title>
                <description>foo</description>
            </photoset>
            <photoset id="4" primary="1234" secret="832659"
                    server="3" photos="12">
                <title>My Set</title>
                <description>bar</description>
            </photoset>
        </photosets>
    </rsp>

## 2.2 Response parser: ElementTree

The old XMLNode parser had some drawbacks. A better one is Python's standard ElementTree. If you create the `FlickrAPI` instance like this, you'll use ElementTree:

    flickr = flickrapi.FlickrAPI(api_key)

or explicitly:

    flickr = flickrapi.FlickrAPI(api_key, format='etree')

The ElementTree documentation is quite clear, but to make things even easier, here are some examples using the same call and response XML as in the XMLNode example:

    sets = flickr.photosets_getList(user_id='73509078@N00')

    sets.attrib['stat'] => 'ok'
    sets.find('photosets').attrib['cancreate'] => '1'

    set0 = sets.find('photosets').findall('photoset')[0]

    +-------------------------------+-----------+
    | variable                      | value     |
    +-------------------------------+-----------+
    | set0.attrib['id']             | u'5'      |
    | set0.attrib['primary']        | u'2483'   |
    | set0.attrib['secret']         | u'abcdef' |
    | set0.attrib['server']         | u'8'      |
    | set0.attrib['photos']         | u'4'      |
    | set0.title[0].text            | u'Test'   |
    | set0.description[0].text      | u'foo'    |
    | set0.find('title').text       | 'Test'    |
    | set0.find('description').text | 'foo'     |
    +-------------------------------+-----------+

    ... and similar for set1 ...

ElementTree is a more mature, better thought out XML parsing framework. It has several advantages over the old XMLNode parser:

> 1. As a standard XML representation, ElementTree will be easier to plug into existing software.
> 2. Easier to iterate over elements. For example, to list all "title" elements, you only need to do `sets.getiterator('title')`.
> 3. Developed by the Python team, which means it's subject to more rigorous testing and has a wider audience than the Python Flickr API module. This will result in a higher quality and less bugs.

## 2.3 ElementTree in Python 2.4

Python 2.5 comes shipped with ElementTree. To get it running on Python 2.4 you'll have to install ElementTree yourself. The easiest way is to get setuptools and then just type:

    easy_install elementtree
    easy_install flickrapi

That'll get you both ElementTree and the latest version of the Python Flickr API.

Another method is to get the Python FlickrAPI source and run:

    python setup.py install
    easy_install elementtree

As a last resort, you can download ElementTree and install it manually.

## 2.4 Response parser: XMLNode

The XMLNode objects are quite simple. Attributes in the XML are converted to dictionary keys with unicode values. Subelements are stored in properties.

We assume you did `sets = flickr.photosets_getList(...)`. The `sets` variable will be structured as such:

    sets['stat'] = 'ok'
    sets.photosets[0]['cancreate'] = u'1'
    sets.photosets[0].photoset = < a list of XMLNode objects >

    set0 = sets.photosets[0].photoset[0]
    set1 = sets.photosets[0].photoset[1]

    +--------------------------+-----------+
    | variable                 | value     |
    +--------------------------+-----------+
    | set0['id']               | u'5'      |
    | set0['primary']          | u'2483'   |
    | set0['secret']           | u'abcdef' |
    | set0['server']           | u'8'      |
    | set0['photos']           | u'4'      |
    | set0.title[0].text       | u'Test'   |
    | set0.description[0].text | u'foo'    |
    +--------------------------+-----------+
    | set1['id']               | u'4'      |
    | set1['primary']          | u'1234'   |
    | set1['secret']           | u'832659' |
    | set1['server']           | u'3'      |
    | set1['photos']           | u'12'     |
    | set1.title[0].text       | u'My Set' |
    | set1.description[0].text | u'bar'    |
    +--------------------------+-----------+

Every `XMLNode` also has a `name` property. The content of this property is left as an exercise for the reader.

As of version 1.2 of the Python Flickr API this XMLNode parser is no longer the default parser, in favour of the ElementTree parser. XMLNode is still supported, though.

## 2.5 Erroneous calls

When something has gone wrong Flickr will return an error code and a description of the error. In this case, a `FlickrError` exception will be thrown.

The old behaviour of the Python Flickr API was to simply return the error code in the XML not raising any exception. It was possible to pass `fail_on_error=False` to the `FlickrAPI` constructor to get this behaviour, but this was deprecated in version 1.1 and has been removed in version 1.3.

## 2.6 Unparsed response formats

Flickr supports different response formats, such as JSON and XML-RPC. If you want, you can use such a different response format. Just add a `format="json"` option to the Flickr call. The Python Flickr API won't parse that format for you, though, so you just get the raw response:

    >>> f = flickrapi.FlickrAPI(api_key)
    >>> f.test_echo(boo='baah', format='json')
    'jsonFlickrApi({"format":{"_content":"json"},
      "auth_token":{"_content":"xxxxx"},
      "boo":{"_content":"baah"},
      "api_sig":{"_content":"xxx"},
      "api_key":{"_content":"xxx"},
      "method":{"_content":"flickr.test.echo"},
      "stat":"ok"})'

If you want all your calls in a certain format, you can also use the `format` constructor parameter:

    >>> f = flickrapi.FlickrAPI(api_key, format='json')
    >>> f.test_echo(boo='baah')
    'jsonFlickrApi({"format":{"_content":"json"},
      "auth_token":{"_content":"xxxxx"},
      "boo":{"_content":"baah"},
      "api_sig":{"_content":"xxx"},
      "api_key":{"_content":"xxx"},
      "method":{"_content":"flickr.test.echo"},
      "stat":"ok"})'

If you use an unparsed format, FlickrAPI won't check for errors. Any format not described in the "Response parser" sections is considered to be unparsed.

# 3 Authentication

Her photos may be private. Access to her account is private for sure. A lot of Flickr API calls require the application to be authenticated. This means that the user has to tell Flickr that the application is allowed to do whatever it needs to do.

The Flickr document User Authentication explains the authentication process; it's good to know what's in there before you go on.

The document states "The auth_token and api_sig parameters should then be passed along with each request". You do not have to do this - the Python Flickr API takes care of that.

Here is a simple example of Flickr's two-phase authentication:

    import flickrapi

    api_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
    api_secret = 'YYYYYYYYYYYYYYYY'

    flickr = flickrapi.FlickrAPI(api_key, api_secret)

    (token, frob) = flickr.get_token_part_one(perms='write')
    if not token: raw_input("Press ENTER after you authorized this program")
    flickr.get_token_part_two((token, frob))

The `api_key` and `api_secret` can be obtained from http://www.flickr.com/services/api/keys/.

The call to `flickr.get_token_part_one(...)` does a lot of things. First, it checks the on-disk token cache. After all, the application may be authenticated already.

If the application isn't authenticated, a browser opens the Flickr page, on which the user can grant the application the appropriate access. The application has to wait for the user to do this, hence the `raw_input("Press ENTER after you authorized this program")`. A GUI application can use a popup for this, or some other way for the user to indicate she has performed the authentication ritual.

Once this step is done, we can continue to store the token in the cache and remember it for future API calls. This is what `flickr.get_token_part_two(...)` does.

## 3.1 Authentication callback

By default a webbrowser is started to let the user perform the authentication. However, this may not be appropriate or even possible in your application. If you want to alter this functionality, use the `auth_callback` parameter when calling `get_token_part_one(...)`. The function will be passed the frob and the requested permission:

    def auth(frob, perms):
        print 'Please give us permission %s' % perms

    (token, frob) = flickr.get_token_part_one(perms='write', auth)

Of course this example isn't useful, but it shows how to use the callback. If you just want to wrap the browser startup with some code, call `flickr.validate_frob(frob, perms)` from your callback.

## 3.2 Authenticating web applications

When working with web applications, things are a bit different. The user using the application (through a browser) is likely to be different from the user running the server-side software.

We'll assume you're following Flickr's Web Applications How-To, and just tell you how things are splified when working with the Python Flickr API.

> 3. Create a login link. Use `flickr.web_login_url(perms)`` for that. It'll return the login link for you, given the permissions you passed in the `perms` parameter.
> 5. Don't bother understanding the signing process; the `FlickrAPI` module takes care of that for you. Once you received the frob from Flickr, use `flickr.get_token("the_frob")`. The FlickrAPI module will remember the token for you.
> 6. You can safely skip this, and just use the FlickrAPI module as usual. Only read this if you want to understand how the FlickrAPI module signs method calls for you.

## 3.3 Token handling in web applications

Web applications have two kinds of users: identified and anonymous users. If your users are identified, you can pass their name (or other means of identification) as the `username` parameter to the `FlickrAPI` constructor, and get a FlickrAPI instance that's bound to that user. It will keep track of the authentication token for that user, and there's nothing special you'll have to do.

When working with anonymous users, you'll have to store the authentication token in a cookie. In step 5. above, use this:

    token = flickr.get_token("the_frob")

Then use your web framework to store the token in a cookie. When reading a token from a cookie, pass it on to the FlickrAPI constructor like this:

    flickr = flickrapi.FlickrAPI(api_key, api_secret, token=token)

It won't be stored in the on-disk token cache - which is a good thing, since

> 1. you don't know who the user is, so you wouldn't be able to retrieve the appropriate tokens for visiting users.
> 2. the tokens are stored in cookies, so there is no need to store them in another place.

## 3.4 Preventing usage of on-disk token cache

Another way of preventing the storage of tokens is to pass `store_token=False` as the constructor parameter. Use this if you want to be absolutely sure that the FlickrAPI instance doesn't use any previously stored tokens, nor that it will store new tokens.

## 3.5 Controlling the location of the on-disk token cache

By default the authentication tokens are stored in the directory `~/.flickr`. If you want to change this directory, you can do so by changing the `flickr.token.path` variable after you have created the `FlickrAPI` instance:

    import flickrapi

    api_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
    api_secret = 'YYYYYYYYYYYYYYYY'

    flickr = flickrapi.FlickrAPI(api_key, api_secret)
    flickr.token.path = '/tmp/flickrtokens'

    (token, frob) = flickr.get_token_part_one(perms='write')
    if not token: raw_input("Press ENTER after you authorized this program")
    flickr.get_token_part_two((token, frob))

## 3.6 Multiple processes using the same key

By default the token is stored on the filesystem in `somepath/<authentication key>/auth.token`. When multiple processes use the same authentication key a race condition can occur where the authentication token is removed. To circumvent this, use the `LockingTokenCache` instead:

    from flickrapi import FlickrAPI
    from flickrapi.tokencache import LockingTokenCache

    flickr = flickrapi.FlickrAPI(api_key, secret)

    flickr.token_cache = LockingTokenCache(api_key)
    # -- or --
    flickr.token_cache = LockingTokenCache(api_key, username)

This cache ensures that only one process at the time can use the token cache. It does not forsee in multi-threading.

As the locking mechanism causes additional disk I/O and performs more checks, it is slower than the regular cache. Since not that many people use the same key in parallel on one machine (or a shared filesystem on which the token is stored) the default token cache does not use locking.

## 3.7 Example using Django

Here is a simple example in Django:

    import flickrapi
    from django.conf import settings
    from django.http import HttpResponseRedirect, HttpResponse

    import logging
    logging.basicConfig()

    log = logging.getLogger(__name__)
    log.setLevel(logging.DEBUG)

    def require_flickr_auth(view):
        '''View decorator, redirects users to Flickr when no valid
        authentication token is available.
        '''

        def protected_view(request, *args, **kwargs):
            if 'token' in request.session:
                token = request.session['token']
                log.info('Getting token from session: %s' % token)
            else:
                token = None
                log.info('No token in session')

           f = flickrapi.FlickrAPI(settings.FLICKR_API_KEY,
                   settings.FLICKR_API_SECRET, token=token,
                   store_token=False)

            if token:
                # We have a token, but it might not be valid
                log.info('Verifying token')
                try:
                    f.auth_checkToken()
                except flickrapi.FlickrError:
                    token = None
                    del request.session['token']

            if not token:
                # No valid token, so redirect to Flickr
                log.info('Redirecting user to Flickr to get frob')
                url = f.web_login_url(perms='read')
                return HttpResponseRedirect(url)

            # If the token is valid, we can call the decorated view.
            log.info('Token is valid')

            return view(request, *args, **kwargs)

        return protected_view

    def callback(request):
        log.info('We got a callback from Flickr, store the token')

       f = flickrapi.FlickrAPI(settings.FLICKR_API_KEY,
               settings.FLICKR_API_SECRET, store_token=False)

        frob = request.GET['frob']
        token = f.get_token(frob)
        request.session['token'] = token

        return HttpResponseRedirect('/content')

    @require_flickr_auth
    def content(request):
        return HttpResponse('Welcome, oh authenticated user!')

Every view that calls an authenticated Flickr method should be decorated with `@require_flickr_auth`. For more information on function decorators, see PEP 318.

The `callback` view should be called when the user is sent to the callback URL as defined in your Flickr API key. The key and secret should be configured in your settings.py, in the properties `FLICKR_API_KEY` and `FLICKR_API_SECRET`.

# 4 Uploading or replacing images

Transferring images requires special attention since they have to send a lot of data. Therefore they also are a bit different than advertised in the Flickr API documentation.

## 4.1 flickr.upload(...)

The `flickr.upload(...)` method has the following parameters:

`filename`
  The filename of the image. The image data is read from this file.
`title`
  The title of the photo
`description`
  The description of the photo
`tags`

  Space-delimited list of tags. Tags that contain spaces need to be quoted. For example:

      tags='''Amsterdam "central station"'''

  Those are two tags, "Amsterdam" and "central station".

`is_public`
  "1" if the photo is public, "0" if it is private. The default is public.
`is_family`
  "1" if the private photo is visible for family, "0" if not. The default is not.
`is_friend`
  "1" if the private photo is visible for friends, "0" if not. The default is not.
`callback`

  This should be a method that receives two parameters, `progress` and `done`. The callback method will be called every once in a while during uploading. Example:

      def func(progress, done):
          if done:
              print "Done uploading"
          else:
              print "At %s%%" % progress

      flickr.upload(filename='test.jpg', callback=func)

`format`
  The response format. This must be either `rest` or one of the parsed formats `etree` / `xmlnode`.

## 4.2 flickr.replace(...)

The `flickr.replace(...)` method has the following parameters:

`filename`
  The filename of the image.
`photo_id`
  The identifier of the photo that is to be replaced. Do not use this when uploading a new photo.
`format`
  The response format. This must be either `rest` or one of the parsed formats `etree` / `xmlnode`.

Only the image itself is replaced, not the other data (title, tags, comments, etc.).

# 5 Unicode and UTF-8

Flickr expects every text to be encoded in UTF-8. The Python Flickr API can help you in a limited way. If you pass a `unicode` string, it will automatically be encoded to UTF-8 before it's sent to Flickr. This is the preferred way of working, and is also forward-compatible with the upcoming Python 3.

If you do not use `unicode` strings, you're on your own, and you're expected to perform the UTF-8 encoding yourself.

Here is an example:

    flickr.photos_setMeta(photo_id='12345',
                          title=u'Money',
                          description=u'Around \u20ac30,-')

This sets the photo's title to "Money" and the description to "Around €30,-".

# 6 Caching of Flickr API calls

There are situations where you call the same Flickr API methods over and over again. An example is a web page that shows your latest ten sets. In those cases caching can significantly improve performance.

The FlickrAPI module comes with its own in-memory caching framework. By default it caches at most 200 entries, which time out after 5 minutes. These defaults are probably fine for average use. To use the cache, just pass `cache=True` to the constructor:

    flickr = flickrapi.FlickrAPI(api_key, cache=True)

To tweak the cache, instantiate your own instance and pass it some constructor arguments:

    flickr = flickrapi.FlickrAPI(api_key, cache=True)
    flickr.cache = flickrapi.SimpleCache(timeout=300, max_entries=200)

`timeout` is in seconds, `max_entries` in number of cached entries.

## 6.1 Using the Django caching framework

The caching framework was designed to have the same interface as the Django low-level cache API - thanks to those guys for designing a simple and effective cache. The result is that you can simply plug the Django caching framework into FlickrAPI, like this:

    from django.core.cache import cache
    flickr = flickrapi.FlickrAPI(api_key, cache=True)
    flickr.cache = cache

That's all you need to enable a wealth of caching options, from database-backed cache to multi-node in-memory cache farms.

# 7 Utility methods

There are a couple of useful methods for handling photos.

All utility methods require ElementTree to be available, so either use Python 2.5 or newer, or install it as described above.

## 7.1 Walking through all photos in a set

It may be useful to be able to easily perform an operation on every photo in a set. This is what the `walk_set` function does. It accepts a photoset ID and returns a generator:

    flickr = flickrapi.FlickrAPI(api_key)
    for photo in flickr.walk_set('2b640a3efc262f03567ee93cfd544e14'):
        print photo.get('title')

The function uses the Flickr API call flickr.photosets.getPhotos and accepts the same parameters. The resulting "photo" objects are ElementTree objects for the `<photo .../>` XML elements.

## 7.2 Walking through a search result

Walking through a search result is done in much the same way as walking through all photos in a set:

    flickr = flickrapi.FlickrAPI(api_key)
    for photo in flickr.walk(tag_mode='all',
            tags='sybren,365,threesixtyfive',
            min_taken_date='2008-08-20',
            max_taken_date='2008-08-30'):
        print photo.get('title')

The function uses the Flickr API call flickr.photos.search and accepts the same parameters. The resulting "photo" objects are ElementTree objects for the `<photo .../>` XML elements.

## 7.3 Influencing the number of calls to Flickr

The walking functions described above only call Flickr when they have to. When they do, they fetch `per_page` (default 50) photos simultaneously. The `per_page` parameter can be used to tweak the number of calls. The following will perform two calls two Flickr:

    flickr = flickrapi.FlickrAPI(api_key)
    set = flickr.walk_set('<set id>', per_page=15)
    for photo in set[:25]:
        print photo.get('title')

The first call will get photos 0-14, the next call will get 15-29, even though only the first 25 photo titles will be shown.

Another example, if you only want to show the titles of photos 5-20:

    flickr = flickrapi.FlickrAPI(api_key)
    set = flickr.walk_set('<set id>' per_page=20)
    for photo in set[5:21]:
        print photo.get('title')

The photos will always be fetched from the first page onwards. In the above example, the first twenty photos will all be fetched, even though the title of the first five will be skipped.

# 8 Short Flickr URLs

Flickr supports linking to a photo page using a short url such as http://flic.kr/p/6BTTT6. The `flickrapi.shorturl` module contains functionality for working with those short URLs.

`flickrapi.shorturl.encode(photo ID)`:
  Returns the short ID for this photo ID
`flickrapi.shorturl.encode(short ID)`:
  Returns the photo ID for this short ID
`flickrapi.shorturl.url(photo ID)`:
  Returns the short URL for the given photo ID.

The photo ID, the short ID and the short URL are all unicode strings.

# 9 Requirements and compatibility

The Python Flickr API only uses built-in Python modules. It is compatible with Python 2.4 and newer.

Usage of the "etree" format requires Python 2.5 or newer.

Rendering the documentation requires Docutils.

# 10 Links

- Python Flickr API interface
- Flickr
- Flickr API documentation