diff --git a/gistapi/core.py b/gistapi/core.py index 8dce053..cf7da70 100644 --- a/gistapi/core.py +++ b/gistapi/core.py @@ -31,44 +31,58 @@ False u'My .bashrc configuration' """ - +import os.path import urllib +import urllib2 from dateutil.parser import parse as dtime - try: import simplejson as json except ImportError: import json - - __all__ = ['Gist', 'Gists'] +# Set your own credentials +USERNAME = 'YOUR GITHUB USERNAME' +TOKEN = 'YOUR GITHUB TOKEN' + +GIST_BASE = 'http://gist.github.com/%s' +GIST_JSON = GIST_BASE % 'api/v1/json/%s' + class Gist(object): """Gist Object""" - def __init__(self, id=None, json=None): + def __init__(self, id=None, json=None, username=None, token=None): self.id = id self._json = json + self._username = username + self._token = token # Map given repo id to gist id if none exists if self._json: self.id = json['repo'] + self.url = url = GIST_BASE % self.id + self.embed_url = url + '.js' + self.epic_embed_url = url + '.pibb' + self.json_url = url + '.json' + self.post_url = GIST_BASE % 'gists/%s' % self.id + def __getattribute__(self, name): - """Gets attributes, but only if needed""" + """Get attributes, but only if needed.""" # Only make external API calls if needed - if name in ['owner', 'description', 'created_at', 'public', 'files', 'filenames', 'repo']: + if name in ('owner', 'description', 'created_at', 'public', + 'files', 'filenames', 'repo'): if not hasattr(self, '_meta'): self._meta = self._get_meta() return object.__getattribute__(self, name) def _get_meta(self): - """Fetches Gist metadata""" + """Fetch Gist metadata.""" # Use json data provided if available if self._json: @@ -76,41 +90,108 @@ class Gist(object): setattr(self, 'id', _meta['repo']) else: # Fetch Gist metadata - _meta_url = 'http://gist.github.com/api/v1/json/%s' % (self.id) - _meta = json.load(urllib.urlopen(_meta_url))['gists'][0] - - self.url = 'http://github.com/%s' % (self.id) - self.embed_url = 'http://github.com/%s.js' % (self.id) - self.epic_embed_url = 'http://github.com/%s.pibb' % (self.id) - self.json_url = 'http://github.com/%s.json' % (self.id) + _meta_url = GIST_JSON % self.id + _meta = json.load(urllib2.urlopen(_meta_url))['gists'][0] for key, value in _meta.iteritems(): if key == 'files': - # Remap file key from API + # Remap file key from API. setattr(self, 'filenames', value) + # Store the {current_name: original_name} mapping. + setattr(self, '_renames', dict((fn, fn) for fn in value)) elif key == 'public': # Attach booleans setattr(self, key, value) elif key == 'created_at': # Attach datetime setattr(self, key, dtime(value)) - else: # Attach properties to object setattr(self, key, unicode(value)) return _meta + def _post(self, params, headers={}): + """POST to the web form (internal method).""" + request = urllib2.Request(self.post_url, + urllib.urlencode(params), + headers) + try: + response = urllib2.urlopen(request) + except IOError, exc: + response = exc + return response.code, response.msg + + def reset(self): + """Clear the local cache.""" + if hasattr(self, '_files'): + del self._files + if hasattr(self, '_meta'): + del self._meta + + def auth(self, username, token): + """Set credentials.""" + self._username = username + self._token = token + + def rename(self, from_name, to_name): + """Rename a file.""" + if from_name not in self.files: + raise KeyError('File %r does not exist' % from_name) + if to_name in self.files: + raise KeyError('File %r already exist' % to_name) + self.files[to_name] = self.files.pop(from_name) + try: + self._renames[to_name] = self._renames.pop(from_name) + except KeyError: + # New file + pass + + def save(self): + """Upload the changes to Github.""" + params = { + '_method': 'put', + 'login': self._username or USERNAME, + 'token': self._token or TOKEN, + } + names_map = self._renames + original_names = names_map.values() + index = len(original_names) + for fn, content in self.files.items(): + ext = os.path.splitext(fn)[1] or '.txt' + try: + orig = names_map.pop(fn) + except KeyError: + # Find a unique filename + while True: + orig = 'gistfile%s' % index + index += 1 + if orig not in original_names: + break + params.update({ + 'file_name[%s]' % orig: fn, + 'file_ext[%s]' % orig: ext, + 'file_contents[%s]' % orig: content, + }) + code, msg = self._post(params=params) + if code == 200: # OK + # If successful, clear the cache + self.reset() + return code, msg + @property def files(self): - """Fetches a gists files and stores them in the 'files' property""" - _files = {} + """Fetch Gist's files and store them in the 'files' property.""" + try: + return self._files + except AttributeError: + self._files = _files = {} - for fn in self.filenames: + for fn in self._meta.files: # Grab file contents - _file_url = 'http://gist.github.com/raw/%s/%s' % (self.id, fn) - _files[fn] = unicode(urllib.urlopen(_file_url).read()) + _file_url = GIST_BASE % 'raw/%s/%s' % (self.id, fn) + _files[fn] = urllib2.urlopen(_file_url).read() return _files @@ -119,16 +200,17 @@ class Gists(object): """Gist API wrapper""" def __init__(self, username=None, token=None): - # Token-based Authentication is unnecesary, gist api still in alpha + # Token-based Authentication is unnecessary, gist api still in alpha self._username = username self._token = token @staticmethod def fetch_by_user(name): - """Returns a set of public Gist objects owned by the given GitHub username""" + """Return a list of public Gist objects owned by + the given GitHub username.""" - _url = 'http://gist.github.com/api/v1/json/gists/%s' % (name) + _url = GIST_JSON % 'gists/%s' % name # Return a list of Gist objects - return [Gist(json=g) for g in json.load(urllib.urlopen(_url))['gists']] - + return [Gist(json=g) + for g in json.load(urllib2.urlopen(_url))['gists']]