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:
parent
6f2162e9a4
commit
0a35922d12
4 changed files with 113 additions and 73 deletions
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue