from __future__ import unicode_literals

import json
import logging
import time
import urllib2

logger = logging.getLogger(__name__)


class Dirble(object):
    """Light wrapper for Dirble API lookup.

    Important things to note:
    - The client will do exponential back off when requests fail or timeout.
    - The client will cache results aggressively.
    - Failed requests will return an empty default type appropriate for the
      lookup in question, normally a empty dict or list.
    - The data returned comes direct from the API's JSON.
    - For stations, only the minimal data set returned by station lists is
      guaranteed to be there.
    """
    def __init__(self, api_key, timeout):
        self._base_uri = 'http://dirble.com/dirapi/%s/apikey/' + api_key
        self._cache = {}
        self._stations = {}
        self._timeout = timeout / 1000.0
        self._backoff_until = time.time()
        self._backoff_max = 60
        self._backoff = 1

    def flush(self):
        self._cache = {}
        self._stations = {}

    def categories(self):
        uri = self._base_uri % 'primaryCategories'
        return self._fetch(uri, [])

    def sub_categories(self, id):
        uri = (self._base_uri + '/primaryid/%s') % ('childCategories', id)
        return self._fetch(uri, [])

    def stations(self, id):
        uri = (self._base_uri + '/id/%s') % ('stations', id)
        stations = self._fetch(uri, [])
        for station in stations:
            self._stations.setdefault(station['id'], station)
        return stations

    def station(self, id):
        id = int(id)  # Ensure we are consistent for cache key.
        if id in self._stations:
            return self._stations[id]
        uri = (self._base_uri + '/id/%s') % ('station', id)
        station = self._fetch(uri, {})
        if station:
            self._stations.setdefault(station['id'], station)
        return station

    def _fetch(self, uri, default):
        if uri in self._cache:
            logger.debug('Cache hit: %s', uri)
            return self._cache[uri]

        if time.time() < self._backoff_until:
            logger.debug('Back off fallack used: %s', uri)
            return default

        logger.debug('Fetching: %s', uri)
        try:
            fp = urllib2.urlopen(uri, timeout=self._timeout)
            data = json.load(fp)
            self._cache[uri] = data
            self._backoff = 1
            return data
        except urllib2.HTTPError as e:
            logger.debug('Fetch failed, HTTP %s: %s', e.code, e.reason)
            if e.code == 404:
                return default  # Place marker in cache?
        except IOError as e:
            logger.debug('Fetch failed: %s', e)
        except ValueError as e:
            logger.warning('Fetch failed: %s', e)

        self._backoff = min(self._backoff_max, self._backoff*2)
        self._backoff_until = time.time() + self._backoff
        logger.debug('Entering back off mode for %d seconds.', self._backoff)
        return default

@@ -1078,31 +1078,27 @@
     
-uri =
+return
  self._
-base_uri %25 
+fetch(
 'pri
@@ -1116,39 +1116,12 @@
 ies'
-%0A        return self._fetch(uri
+, ''
 , %5B%5D
@@ -1144,34 +1144,40 @@
 ategories(self, 
-id
+category
 ):%0A        uri =
@@ -1171,39 +1171,22 @@
         
-uri = (self._base_uri +
+path =
  '/prima
@@ -1197,35 +1197,19 @@
 /%25s'
-)
  %25 
-('childCategories', id)
+category
 %0A   
@@ -1228,27 +1228,47 @@
 self._fetch(
-uri
+'childCategories', path
 , %5B%5D)%0A%0A    d
@@ -1281,26 +1281,32 @@
 tions(self, 
-id
+category
 ):%0A        u
@@ -1304,39 +1304,22 @@
         
-uri = (self._base_uri +
+path =
  '/id/%25s
@@ -1323,28 +1323,19 @@
 /%25s'
-)
  %25 
-('stations', id)
+category
 %0A   
@@ -1362,19 +1362,32 @@
 ._fetch(
-uri
+'stations', path
 , %5B%5D)%0A  
@@ -1528,16 +1528,24 @@
 self, id
+entifier
 ):%0A     
@@ -1549,16 +1549,24 @@
       id
+entifier
  = int(i
@@ -1566,16 +1566,24 @@
 = int(id
+entifier
 )  # Ens
@@ -1632,16 +1632,24 @@
    if id
+entifier
  in self
@@ -1696,16 +1696,24 @@
 tions%5Bid
+entifier
 %5D%0A      
@@ -1718,31 +1718,14 @@
     
-uri = (self._base_uri +
+path =
  '/i
@@ -1733,27 +1733,21 @@
 /%25s'
-)
  %25 
-('station', id)
+identifier
 %0A   
@@ -1773,19 +1773,31 @@
 ._fetch(
-uri
+'station', path
 , %7B%7D)%0A  
@@ -1925,22 +1925,80 @@
 lf, 
-uri, default):
+variant, path, default):%0A        uri = (self._base_uri %25 variant) + path
 %0A   
@@ -2194,16 +2194,17 @@
 off fall
+b
 ack used

Update client internal API as prep for refactor.