text: import json
  import os
  import re
  import requests
  try:
      from urllib import urlencode
      from urlparse import urlunparse
  except ImportError:
      from urllib.parse import urlencode
      from urllib.parse import urlunparse
  
  _JSON_HEADERS = {
      'accepts': 'application/json',
      'content-type': 'application/json'
  }
  
  ROOT = '/v0/'
  API_ROOT = ROOT + 'api/'
  OAUTH2_ROOT = ROOT + 'oauth2/'
  
  
  DEFAULT_CONFIG = {
      'protocol': 'https',
      'host': 'blot.re',
  }
  
  def _extend(dict1, dict2):
      extended = dict1.copy()
      extended.update(dict2)
      return extended
  
  class TokenEndpointError(Exception):
      """
      Error communicating with the token endpoint.
      """ 
      def __init__(self, error, error_description):
          self.error = error
          self.error_description = error_description
          super(TokenEndpointError, self).__init__(
              "[%s] %s" % (self.error, self.error_description))
          
  def _token_error_from_data(data):
      return TokenEndpointError(
          data.get('error', ''),
          data.get('error_description', ''))
  
  class RestError(Exception):
      """
      Error response from one of the REST APIS.
      """ 
      def __init__(self, status_code, error_description, details):
          self.status_code = status_code
          self.error_description = error_description
          self.details = details
          super(RestError, self).__init__(
              "[%s] %s" % (self.status_code, self.error_description))
  
  def _is_error_response(body):
      return body.get('type', '') == 'Error' or 'error' in body
  
  def _rest_error_from_response(response):
      body = response.json()
      return RestError(
          response.status_code,
          body['error'],
          body.get('details', None))
  
  def _format_url(config, relPath, query={}):
      return urlunparse((
          config.get('protocol'),
          config.get('host'),
          relPath,
          '',
          urlencode(query),
          ''))
  
  class Blotre:
      """
      Main Blot're flow object.
      """
      def __init__(self, client, creds={}, config={}):
          self.client = client
          self.config = _extend(DEFAULT_CONFIG, config)
          self.creds = creds
          
      def set_creds(self, newCreds):
          """Manually update the current creds."""
          self.creds = newCreds
          self.on_creds_changed(newCreds)
          return self
          
      def on_creds_changed(self, newCreds):
          """
          Overridable function called when the creds change
          """
          pass
          
      def normalize_uri(self, uri):
          """Convert a stream path into it's normalized form."""
          return urllib.quote(
              re.sub(r"\s", '+', uri.strip().lower()),
              safe = '~@#$&()*!+=:),.?/\'')
      
      def join_uri(self, *paths):
          return '/'.join(self.normalize_uri(x) for x in paths)
      
      def _get_websocket_protocol(self):
          return 'ws' if self.config.protocol == 'http' else 'wss'
              
      def get_websocket_url(self):
          """
          Get the url for using the websocked APIs,
          used for both subscription and send/receive.
          """
          return self._get_websocket_protocol() + '://' + config.host + '/v0/ws'
      
      def _format_url(self, relPath, query={}):
          return _format_url(self.config, relPath, query)
              
  # Authorization
      def get_authorization_url(self):
          """Get the authorization Url for the current client."""
          return self._format_url(
              OAUTH2_ROOT + 'authorize',
              query = {
                  'response_type': 'code',
                  'client_id': self.client.get('client_id', ''),
                  'redirect_uri': self.client.get('redirect_uri', '')
              })
      
      def get_redeem_url(self):
          """
          Get the Url where a user can redeem a onetime code
          for a disposable client.
          """
          return self._format_url(OAUTH2_ROOT + 'redeem')
              
      def _access_token_endpoint(self, grantType, extraParams={}):
          """
          Base exchange of data for an access_token.
          """
          response = requests.post(
              self._format_url(OAUTH2_ROOT + 'access_token'),
              data = _extend({
                  'grant_type': grantType,
                  'client_id': self.client.get('client_id', ''),
                  'client_secret': self.client.get('client_secret', ''),
                  'redirect_uri': self.client.get('redirect_uri', '')
              }, extraParams))
          
          data = response.json()
          if 'error' in data or 'error_description' in data:
              raise _token_error_from_data(data)
          else:
              return self.set_creds(data)
              
      def redeem_authorization_code(self, code):
          """
          Exchange an authorization code for a new access token.
          If successful, update client to use these credentials.
          """
          return self._access_token_endpoint('authorization_code', {
              'code': code
          })
          
      def exchange_refresh_token(self):
          """
          Attempt to exchange a refresh token for a new access token.
          If successful, update client to use these credentials.
          """
          return self._access_token_endpoint('refresh_token', {
              'refresh_token': self.creds['refresh_token']
          })
              
      def redeem_onetime_code(self, code):
          """
          Attempt to exchange a onetime token for a new access token.
          If successful, update client to use these credentials.
          """
          return self._access_token_endpoint(
              'https://oauth2grant.blot.re/onetime_code', {
                  'code': code if code else self.client['code']
              })
      
      def get_token_info(self):
          """
          Get information about the current access token.
          """
          response = requests.get(
              self._format_url(OAUTH2_ROOT + 'token_info', {
                  'token': self.creds['access_token']
              }))
          data = response.json()
          if response.status_code != 200:
              raise _token_error_from_data(data)
          else:
              return data
          
  # Requests
      def _add_auth_headers(self, base):
          """Attach the acces_token to a request."""
          if 'access_token' in self.creds:
              return _extend(base, {
                  'authorization': 'Bearer ' + self.creds['access_token']
              })
          return base
      
      def _is_expired_response(self, response):
          """
          Check if the response failed because of an expired access token.
          """
          if response.status_code != 401:
              return False
          challenge = response.headers.get('www-authenticate', '')
          return 'error="invalid_token"' in challenge
  
      def _make_request(self, type, path, args, noRetry=False):
          """
          Make a request to Blot're.
          
          Attempts to reply the request if it fails due to an expired
          access token.
          """
          response = getattr(requests, type)(path, headers = self._add_auth_headers(_JSON_HEADERS), **args)
          if response.status_code == 200 or response.status_code == 201:
              return response.json()
          elif not noRetry and self._is_expired_response(response) \
            and 'refresh_token' in self.creds:
              try:
                  self.exchange_refresh_token()
              except TokenEndpointError:
                  raise _rest_error_from_response(response)
              return self._make_request(type, path, args, noRetry = True)
          raise _rest_error_from_response(response)
                  
      def get(self, path, query={}):
          """GET request."""
          return self._make_request('get',
              self._format_url(API_ROOT + path, query=query), {})
          
      def post(self, path, body):
          """POST request."""
          return self._make_request('post',
              self._format_url(API_ROOT + path), {
                  'json': body
              })
              
      def put(self, path, body):
          """PUT request."""
          return self._make_request('put',
              self._format_url(API_ROOT + path), {
                  'json': body
              })
              
      def delete(self, path):
          """DELETE request."""
          return self._make_request('get',
              self._format_url(API_ROOT + path), {})
  
  # User Operations
      def get_user(self, userId, options={}):
          """Get a user by id."""
          return self.get('user/' + userId, options)
      
  # Stream Operations
      def get_streams(self, options={}):
          """Stream lookup."""
          return self.get('stream', options)
  
      def create_stream(self, body):
          """Create a new stream."""
          return self.put('stream', body)
  
      def get_stream(self, id, options={}):
          """Get a stream."""
          return self.get('stream/' + id, options)
  
      def delete_stream(self, id):
          """Delete an existing stream."""
          return self.delete('stream', id)
  
      def get_stream_status(self, id, options={}):
          """Get the status of a stream."""
          return self.get('stream/' + id, options)
  
      def set_stream_status(self, id, body):
          """Update the status of a stream."""
          return self.post('stream/' + id + '/status', body)
  
      def get_stream_children(self, id, options={}):
          """Get the children of a stream."""
          return self.get('stream/' + id + '/children', options)
  
      def get_child(self, streamId, childId, options={}):
          """Get the child of a stream."""
          return self.get('stream/' + streamId + '/children/' + childId, options)
  
      def create_child(self, streamId, childId):
          """Add a new child to a stream."""
          return self.put('stream/' + streamId + '/children/' + childId)
  
      def delete_child(self, streamId, childId):
          """Remove a child from a stream."""
          return self.delete('stream/' + streamId + '/children/' + childId)
      
      def get_tags(streamId):
          """Get the tags of a stream."""
          self.get('stream/' + streamId + '/tags')
  
      def set_tags(streamId):
          """Set the tags of a stream."""
          self.post('stream/' + streamId + '/tags')
  
      def get_tag(streamId, tag):
          """Get a tag on a stream."""
          self.get('stream/' + streamId + '/tags/' + tag)
  
      def set_tag(streamId, tag):
          """Create a tag on a stream."""
          self.put('stream/' + streamId + '/tags/' + tag)
  
      def delete_tag(streamId, tag):
          """Remove a tag on a stream."""
          self.delete('stream/' + streamId + '/tags/' + tag)
  
  
  # Basic Disposable Client
  def create_disposable(clientInfo, config = {}):
      """
      Create a new disposable client.
      """
      response = requests.put(
          _format_url(
              _extend(DEFAULT_CONFIG, config), 
              OAUTH2_ROOT + 'disposable'),
          json = clientInfo)
      
      if response.status_code != 200:
          return None
      else:
          body = response.json() 
          return Blotre({
              'client_id': body['id'],
              'client_secret': body['secret'],
              'code': body['code']
          }, config = config)
  
  # Disposable App
  class _BlotreDisposableApp(Blotre):
      def __init__(self, file, client, **kwargs):
          Blotre.__init__(self, client, **kwargs)
          self.file = file
          self._persist()
  
      def on_creds_changed(self, newCreds):
          self._persist()
  
      def _persist(self):
          """Persist client data."""
          with open(self.file, 'w') as f:
              json.dump({
                  'client': self.client,
                  'creds': self.creds,
                  'config': self.config
              }, f)
  
  def _get_disposable_app_filename(clientInfo):
      """
      Get name of file used to store creds.
      """
      return clientInfo.get('file', clientInfo['name'] + '.client_data.json')
      
  def _get_existing_disposable_app(file, clientInfo, conf):
      """
      Attempt to load an existing 
      """
      if not os.path.isfile(file):
          return None
      else:
          data = None
          with open(file, 'r') as f:
              data = json.load(f)
          if not 'client' in data or not 'creds' in data:
              return None
          return _BlotreDisposableApp(file,
              data['client'],
              creds = data['creds'],
              config = conf)
  
  def _try_redeem_disposable_app(file, client):
      """
      Attempt to redeem a one time code registred on the client.
      """
      redeemedClient = client.redeem_onetime_code(None)
      if redeemedClient is None:
          return None
      else:
          return _BlotreDisposableApp(file,
              redeemedClient.client,
              creds = redeemedClient.creds,
              config = redeemedClient.config)
  
  def _create_new_disposable_app(file, clientInfo, config):
      client = create_disposable(clientInfo, config = config)
      if client is None:
          return None
      code = client.client['code']
      userInput = raw_input("Please redeem disposable code: " + code + '\n')
      return _try_redeem_disposable_app(file, client)
  
  def _check_app_is_valid(client):
      """
      Check to see if the app has valid creds.
      """
      try:
          if 'refresh_token' in client.creds:
              client.exchange_refresh_token()
          else:
              existing.get_token_info()
          return True
      except TokenEndpointError as e:
          return False
  
  def create_disposable_app(clientInfo, config={}):
      """
      Use an existing disposable app if data exists or create a new one
      and persist the data.
      """
      file = _get_disposable_app_filename(clientInfo)
      existing = _get_existing_disposable_app(file, clientInfo, config)
      if existing:
          if _check_app_is_valid(existing):
              return existing
          else:
              print("Existing client has expired, must recreate.")
          
      return _create_new_disposable_app(file, clientInfo, config)
  