Move config loader into the OpenPhoto class.

This allows config files to be used everywhere, not just from the commandline.
This commit is contained in:
sneakypete81 2013-05-04 13:15:51 +01:00
parent 6f2162e9a4
commit 0a35922d12
4 changed files with 113 additions and 73 deletions

View file

@ -46,7 +46,7 @@ The OpenPhoto Python class hierarchy mirrors the [OpenPhoto API](http://theopenp
<a name="cli"></a>
### Using from the command line
When using the command line tool, you'll want to export your authentication credentials to the environment.
When using the command line tool, you'll want to export your authentication credentials to the environment.
The command line tool will look for the following config file in ~/.config/openphoto/default
(the -c switch lets you specify a different config file):
@ -63,7 +63,7 @@ These are the options you can pass to the shell program:
-h # Display help text
-c config_file # Either the name of a config file in ~/.config/openphoto/ or a full path to a config file
-H hostname # Overrides config_file for unauthenticated API calls [default=localhost]
-H hostname # Overrides config_file for unauthenticated API calls
-e endpoint # [default=/photos/list.json]
-X method # [default=GET]
-F params # e.g. -F 'title=my title' -F 'tags=mytag1,mytag2'
@ -113,5 +113,8 @@ You can run commands to the OpenPhoto API from your shell!
<a name="credentials"></a>
#### Getting your credentials
You can get your credentals by clicking on the arrow next to your email address once you're logged into your site and then clicking on settings.
If you don't have any credentials then you can create one for yourself using the "Create a new app" button.
To get your credentials:
* Log into your Trovebox site
* Click the arrow on the top-right and select 'Settings'.
* Click the 'Create a new app' button.
* Click the 'View' link beside the newly created app.

View file

@ -5,14 +5,20 @@ import api_tag
import api_album
class OpenPhoto(OpenPhotoHttp):
""" Client library for OpenPhoto """
def __init__(self, host,
"""
Client library for OpenPhoto
If no parameters are specified, config is loaded from the default
location (~/.config/openphoto/default).
The config_file parameter is used to specify an alternate config file.
If the host parameter is specified, no config file is loaded.
"""
def __init__(self, config_file=None, host=None,
consumer_key='', consumer_secret='',
token='', token_secret=''):
OpenPhotoHttp.__init__(self, host,
OpenPhotoHttp.__init__(self, config_file, host,
consumer_key, consumer_secret,
token, token_secret)
self.photos = api_photo.ApiPhotos(self)
self.photo = api_photo.ApiPhoto(self)
self.tags = api_tag.ApiTags(self)

View file

@ -3,8 +3,6 @@ import os
import sys
import string
import urllib
import StringIO
import ConfigParser
from optparse import OptionParser
try:
@ -14,47 +12,6 @@ except ImportError:
from openphoto import OpenPhoto
def get_config_path(config_file):
config_path = os.getenv('XDG_CONFIG_HOME')
if not config_path:
config_path = os.path.join(os.getenv('HOME'), ".config")
if not config_file:
config_file = "default"
return os.path.join(config_path, "openphoto", config_file)
def read_config(config_file):
"""
Loads config data from the specified file.
If config_file doesn't exist, returns an empty authentication config for localhost.
"""
section = "DUMMY"
defaults = {'host': 'localhost',
'consumerKey': '', 'consumerSecret': '',
'token': '', 'tokenSecret':'',
}
# Insert an section header at the start of the config file, so ConfigParser can understand it
# Also prepend a [DEFAULT] section, since it's the only way to specify case-sensitive defaults
buf = StringIO.StringIO()
buf.write("[DEFAULT]\n")
for key in defaults:
buf.write("%s=%s\n" % (key, defaults[key]))
buf.write('[%s]\n' % section)
if os.path.isfile(config_file):
buf.write(open(config_file).read())
else:
print "Config file '%s' doesn't exist - authentication won't be used" % config_file
buf.seek(0, os.SEEK_SET)
parser = ConfigParser.SafeConfigParser()
parser.optionxform = str # Case-sensitive options
parser.readfp(buf)
# Trim quotes
config = parser.items(section)
config = [(item[0], item[1].replace('"', '')) for item in config]
config = [(item[0], item[1].replace("'", "")) for item in config]
return dict(config)
#################################################################
def main(args=sys.argv[1:]):
@ -85,16 +42,28 @@ def main(args=sys.argv[1:]):
# Host option overrides config file settings
if options.host:
config = {'host': options.host, 'consumerKey': '', 'consumerSecret': '',
'token': '', 'tokenSecret': ''}
client = OpenPhoto(host=options.host)
else:
config_path = get_config_path(options.config_file)
config = read_config(config_path)
if options.verbose:
print "Using config from '%s'" % config_path
client = OpenPhoto(config['host'], config['consumerKey'], config['consumerSecret'],
config['token'], config['tokenSecret'])
try:
client = OpenPhoto(config_file=options.config_file)
except IOError as error:
print error
print
print "You must create a configuration file with the following contents:"
print " host = your.host.com"
print " consumerKey = your_consumer_key"
print " consumerSecret = your_consumer_secret"
print " token = your_access_token"
print " tokenSecret = your_access_token_secret"
print
print "To get your credentials:"
print " * Log into your Trovebox site"
print " * Click the arrow on the top-right and select 'Settings'."
print " * Click the 'Create a new app' button."
print " * Click the 'View' link beside the newly created app."
print
print error
sys.exit(1)
if options.method == "GET":
result = client.get(options.endpoint, process_response=False, **params)

View file

@ -1,9 +1,12 @@
import os
import oauth2 as oauth
import urlparse
import urllib
import urllib2
import httplib2
import logging
import StringIO
import ConfigParser
try:
import json
except ImportError:
@ -17,17 +20,37 @@ DUPLICATE_RESPONSE = {"code": 409,
"message": "This photo already exists"}
class OpenPhotoHttp:
""" Base class to handle HTTP requests to an OpenPhoto server """
def __init__(self, host, consumer_key='', consumer_secret='',
"""
Base class to handle HTTP requests to an OpenPhoto server.
If no parameters are specified, config is loaded from the default
location (~/.config/openphoto/default).
The config_file parameter is used to specify an alternate config file.
If the host parameter is specified, no config file is loaded.
"""
def __init__(self, config_file=None, host=None,
consumer_key='', consumer_secret='',
token='', token_secret=''):
self._host = host
self._consumer_key = consumer_key
self._consumer_secret = consumer_secret
self._token = token
self._token_secret = token_secret
self._logger = logging.getLogger("openphoto")
if host is None:
self.config_path = self._get_config_path(config_file)
config = self._read_config(self.config_path)
self._host = config['host']
self._consumer_key = config['consumerKey']
self._consumer_secret = config['consumerSecret']
self._token = config['token']
self._token_secret = config['tokenSecret']
else:
self._host = host
self._consumer_key = consumer_key
self._consumer_secret = consumer_secret
self._token = token
self._token_secret = token_secret
if host is not None and config_file is not None:
raise ValueError("Cannot specify both host and config_file")
# Remember the most recent HTTP request and response
self.last_url = None
self.last_params = None
@ -36,9 +59,9 @@ class OpenPhotoHttp:
def get(self, endpoint, process_response=True, **params):
"""
Performs an HTTP GET from the specified endpoint (API path),
passing parameters if given.
Returns the decoded JSON dictionary, and raises exceptions if an
error code is received.
passing parameters if given.
Returns the decoded JSON dictionary, and raises exceptions if an
error code is received.
Returns the raw response if process_response=False
"""
params = self._process_params(params)
@ -71,9 +94,9 @@ class OpenPhotoHttp:
def post(self, endpoint, process_response=True, files = {}, **params):
"""
Performs an HTTP POST to the specified endpoint (API path),
passing parameters if given.
Returns the decoded JSON dictionary, and raises exceptions if an
error code is received.
passing parameters if given.
Returns the decoded JSON dictionary, and raises exceptions if an
error code is received.
Returns the raw response if process_response=False
"""
params = self._process_params(params)
@ -186,3 +209,42 @@ class OpenPhotoHttp:
return []
else:
return result
@staticmethod
def _get_config_path(config_file):
config_path = os.getenv('XDG_CONFIG_HOME')
if not config_path:
config_path = os.path.join(os.getenv('HOME'), ".config")
if not config_file:
config_file = "default"
return os.path.join(config_path, "openphoto", config_file)
def _read_config(self, config_file):
"""
Loads config data from the specified file.
If config_file doesn't exist, returns an empty authentication config for localhost.
"""
section = "DUMMY"
defaults = {'host': 'localhost',
'consumerKey': '', 'consumerSecret': '',
'token': '', 'tokenSecret':'',
}
# Insert an section header at the start of the config file, so ConfigParser can understand it
# Also prepend a [DEFAULT] section, since it's the only way to specify case-sensitive defaults
buf = StringIO.StringIO()
buf.write("[DEFAULT]\n")
for key in defaults:
buf.write("%s=%s\n" % (key, defaults[key]))
buf.write('[%s]\n' % section)
buf.write(open(config_file).read())
buf.seek(0, os.SEEK_SET)
parser = ConfigParser.SafeConfigParser()
parser.optionxform = str # Case-sensitive options
parser.readfp(buf)
# Trim quotes
config = parser.items(section)
config = [(item[0], item[1].replace('"', '')) for item in config]
config = [(item[0], item[1].replace("'", "")) for item in config]
return dict(config)