PKhGkpigshare/example.py#!/usr/bin/env python import hashlib import json import os import requests from requests.exceptions import HTTPError BASE_URL = 'https://api.figsh.com/v2/{endpoint}' TOKEN = '25c1ae76c46615b7c129ada9acf137c04bb63492162f39c42507f3f90cf54796fe75101564381eae9527532f5d5e9c59153e29b03551a1474acb5ab79bdcf3fe' CHUNK_SIZE = 1048576 FILE_NAME = '/home/markus/examples.desktop' TITLE = 'Test file markus' def raw_issue_request(method, url, data=None): headers = {'Authorization': 'token ' + TOKEN} if data is not None: data = json.dumps(data) response = requests.request(method, url, headers=headers, data=data) try: response.raise_for_status() try: data = json.loads(response.content) except ValueError: data = response.content except HTTPError as error: print 'Caught an HTTPError: {}'.format(error.message) print 'Body:\n', response.content raise return data def issue_request(method, endpoint, *args, **kwargs): return raw_issue_request(method, BASE_URL.format(endpoint=endpoint), *args, **kwargs) def list_articles(): result = issue_request('GET', 'account/articles') print 'Listing current articles:' if result: for item in result: print u' {url} - {title}'.format(**item) else: print ' No articles.' print def create_article(title): data = { 'title': title # You may add any other information about the article here as you wish. } result = issue_request('POST', 'account/articles', data=data) print 'Created article:', result['location'], '\n' result = raw_issue_request('GET', result['location']) return result['id'] def list_files_of_article(article_id): result = issue_request('GET', 'account/articles/{}/files'.format(article_id)) print 'Listing files for article {}:'.format(article_id) if result: for item in result: print ' {id} - {name}'.format(**item) else: print ' No files.' print def get_file_check_data(file_name): with open(file_name, 'rb') as fin: md5 = hashlib.md5() size = 0 data = fin.read(CHUNK_SIZE) while data: size += len(data) md5.update(data) data = fin.read(CHUNK_SIZE) return md5.hexdigest(), size def initiate_new_upload(article_id, file_name): endpoint = 'account/articles/{}/files' endpoint = endpoint.format(article_id) md5, size = get_file_check_data(file_name) data = {'name': os.path.basename(file_name), 'md5': md5, 'size': size} result = issue_request('POST', endpoint, data=data) print 'Initiated file upload:', result['location'], '\n' result = raw_issue_request('GET', result['location']) return result def complete_upload(article_id, file_id): issue_request('POST', 'account/articles/{}/files/{}'.format(article_id, file_id)) def upload_parts(file_info): url = '{upload_url}/{upload_token}'.format(**file_info) result = raw_issue_request('GET', url) print 'Uploading parts:' with open(FILE_NAME, 'rb') as fin: for part in result['parts']: upload_part(file_info, fin, part) print def upload_part(file_info, stream, part): udata = file_info.copy() udata.update(part) url = '{upload_url}/{upload_token}/{partNo}'.format(**udata) stream.seek(part['startOffset']) data = stream.read(part['endOffset'] - part['startOffset'] + 1) raw_issue_request('PUT', url, data=data) print ' Uploaded part {partNo} from {startOffset} to {endOffset}'.format(**part) def main(): # We first create the article list_articles() article_id = create_article(TITLE) list_articles() list_files_of_article(article_id) # Then we upload the file. file_info = initiate_new_upload(article_id, FILE_NAME) # Until here we used the figshare API; following lines use the figshare upload service API. upload_parts(file_info) # We return to the figshare API to complete the file upload process. complete_upload(article_id, file_info['id']) list_files_of_article(article_id) if __name__ == '__main__': main() PKe3HɡDpigshare/helpers.py# helpers def create_models(cls, response): json = response.json() if len(json) >= 1000: print "Max number of results, try to filter." models = [] for item in json: model = create_model_object(cls, item) models.append(model) return models def print_item(model): print model def print_items(models): for m in models: print m def create_model(cls, response): json = response.json() model = create_model_object(cls, json) return model def create_model_object(cls, properties): # TODO, maybe do some validation, error handling model = cls(properties) return model def add_ordering(args, req_params={}): '''Adds parameters for ordering of results.''' # TODO pass def add_filters(args, req_params={}): '''Adds the default filtering arguments (institution, group, published_since, modified_since).''' if args.institution: req_params['institution'] = args.institution if args.group: req_params['group'] = args.group if args.published_since: req_params['published_since'] = args.published_since if args.modified_since: req_params['modified_since'] = args.modified_since return req_params def utf8lize(obj): if isinstance(obj, dict): temp = {} for k, v in obj.iteritems(): temp[k] = to_utf8(v) return temp if isinstance(obj, list): temp = [] for x in obj: temp.append(to_utf8(x)) return temp if isinstance(obj, unicode): return obj.encode('utf-8') return obj def to_utf8(obj): if isinstance(obj, unicode): return obj.encode('utf-8') return obj def querystr(**kwargs): return '?' + urlencode(kwargs) PK3HnC~ZZpigshare/caching.pyimport shelve import os PIGSHARE_DIR = os.path.expanduser('~/.pigshare') try: os.mkdir(PIGSHARE_DIR) except OSError: pass def get_authors_cache(): s = shelve.open('{}/cache.db'.format(PIGSHARE_DIR), writeback=True) authors = s.get('authors', None) if not authors: s['authors'] = {} return s s = None def get_shelve(): global s if not s: s = get_authors_cache() return s def get_authors(): return get_shelve()['authors'] def add_author(id, name): get_authors()[id] = name def close_authors_cache(): get_shelve().close() PKr HNepigshare/pigshare.py# PYTHON_ARGCOMPLETE_OK from signal import signal, SIGPIPE, SIG_DFL import caching import sys # Ignore SIG_PIPE and don't throw exceptions on it... # (http://docs.python.org/library/signal.html) signal(SIGPIPE, SIG_DFL) from api import figshare_api from stats_api import figshare_stats_api as stats_api from stats_api import STATS_API_ID_ARG_MAP from api import API_ARG_MAP import os import ConfigParser import logging from models import * from api import FIGSHARE_BASE_URL from input_helpers import create_article from pyclist.pyclist import pyclist CONF_FILENAME = 'pigshare.conf' CONF_HOME = os.path.expanduser('~/.' + CONF_FILENAME) class PigshareConfig(object): def __init__(self): self.config = ConfigParser.SafeConfigParser({'token': None, 'url': FIGSHARE_BASE_URL, 'institution': None, 'stats_token': None}) try: user = os.environ['SUDO_USER'] conf_user = os.path.expanduser('~' + user + "/." + CONF_FILENAME) candidates = [conf_user, CONF_HOME] except KeyError: candidates = [CONF_HOME] self.config.read(candidates) try: self.figshare_url = self.config.get('default', 'url') except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e: self.figshare_url = FIGSHARE_BASE_URL try: self.figshare_token = self.config.get('default', 'token') except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e: self.figshare_token = None class Pigshare(object): def __init__(self): self.config = PigshareConfig() self.cli = pyclist( 'pigshare', 'A commandline wrapper for the Figshare REST API') self.cli.root_parser.add_argument( '--url', '-u', help='Figshare base url', default=self.config.figshare_url) self.cli.root_parser.add_argument( '--token', '-t', help='Token to connect to figshare', default=self.config.figshare_token) self.cli.root_parser.add_argument( '--profile', '-p', help='Profile to use (profile must be defined in ~/.pigshare.conf), takes precedence over --url and --token config') self.cli.root_parser.add_argument( '--institution', '-i', help='The institution, necessary for some of the stats lookups') self.cli.root_parser.add_argument( '--verbose', '-v', help='Verbose output, for debugging/displaying generated json', action='store_true') self.cli.root_parser.add_argument( '--output', '-o', help='Filter output format') self.cli.root_parser.add_argument( '--separator', '-s', default='\n', help='Seperator for output, useful to create a comma-separated list of ids. Default is new-line') self.cli.add_command(figshare_api, API_ARG_MAP, {'ArticleCreate': create_article, 'CollectionCreate': CollectionCreate}) self.cli.add_command(stats_api, STATS_API_ID_ARG_MAP) self.cli.parse_arguments() self.url = self.cli.namespace.url self.token = self.cli.namespace.token self.institution = self.cli.namespace.institution if self.cli.namespace.profile: self.cli.parameters['url'] = self.config.config.get( self.cli.namespace.profile, 'url') self.cli.parameters['token'] = self.config.config.get( self.cli.namespace.profile, 'token') self.cli.parameters['stats_token'] = self.config.config.get( self.cli.namespace.profile, 'stats_token') self.cli.parameters['institution'] = self.config.config.get( self.cli.namespace.profile, 'institution') if self.institution: self.cli.parameters['institution'] = self.institution self.cli.execute() self.output = self.cli.namespace.output self.separator = self.cli.namespace.separator self.cli.print_result(self.output, self.separator) # print caching.get_authors() caching.close_authors_cache() sys.exit(0) def run(): Pigshare() PKHc & &pigshare/models.pyfrom booby import Model, fields from booby.validators import nullable import json from helpers import * FIGSHARE_BASE_URL = 'https://api.figshare.com/v2' # types: # 1 - figure # 2 - media # 3 - dataset # 4 - fileset # 5 - poster # 6 - paper # 7 - presentation # # 11 - metadata # FIGSHARE_DEFINED_TYPES = ['figure', 'media', 'dataset', 'fileset', 'poster', 'paper', 'presentation', 'thesis', 'code', 'metadata'] FIGSHARE_DEFINED_TYPES_DICT = { 1: "figure", 2: "media", 3: "dataset", 4: "fileset", 5: "poster", 6: "paper", 7: "presentation", 8: "thesis", 9: "code", 11: "metadata" } # Extra validators ======================================== class DateValidator(object): """This validator forces fields values to be an instance of `basestring`.""" @nullable def validate(self, value): if not isinstance(value, basestring): raise errors.ValidationError('should be a string') class DefinedTypeValidator(object): """This validator forces fields values to be an instance of `basestring`.""" @nullable def validate(self, value): if not isinstance(value, basestring): raise errors.ValidationError('should be a string') if value not in FIGSHARE_DEFINED_TYPES_DICT.values(): raise errors.ValidationError( 'should be one of ' + str(FIGSHARE_DEFINED_TYPES_DICT.values())) # Extra models ======================================== class Date(fields.Field): """:class:`Field` subclass with builtin `date` validation.""" def __init__(self, *args, **kwargs): super(Date, self).__init__(DateValidator(), *args, **kwargs) class DefinedType(fields.Field): """:class:`Field` subclass with builtin `DefinedType` validation.""" def __init__(self, *args, **kwargs): super(DefinedType, self).__init__( DefinedTypeValidator(), *args, **kwargs) # Models ======================================== class CustomField(Model): name = fields.String(required=True) value = fields.String() is_mandatory = fields.Boolean() class ArticleShort(Model): id = fields.Integer(required=True) title = fields.String(required=True) doi = fields.String(required=True) url = fields.String(required=True) published_date = Date(required=True) # def __str__(self): # return self.title class Category(Model): id = fields.Integer(required=True) title = fields.String(required=True) class License(Model): name = fields.Integer(required=True) value = fields.String(required=True) url = fields.String() class AuthorCreate(Model): id = fields.Integer() name = fields.String() class ArticleCreate(Model): title = fields.String(required=True) description = fields.String() tags = fields.Collection(fields.String) references = fields.Collection(fields.String) categories = fields.Collection(fields.Integer) authors = fields.Collection(AuthorCreate) custom_fields = fields.Field() defined_type = DefinedType() funding = fields.String() license = fields.Integer() class ArticleL1(Model): id = fields.Integer(required=True) title = fields.String(required=True) doi = fields.String(required=True) url = fields.String(required=True) published_date = Date(required=True) citation = fields.String() confidential_reason = fields.String() embargo_type = fields.String() is_confidential = fields.Boolean() size = fields.Integer() funding = fields.String() tags = fields.Collection(fields.String) version = fields.Integer() is_active = fields.Integer() is_metadata_record = fields.Boolean() metadata_reason = fields.String() status = fields.String() description = fields.String() is_embargoed = fields.Boolean() embargo_date = Date() is_public = fields.Boolean() modified_date = Date() created_date = Date() has_linked_file = fields.Boolean() categories = fields.Collection(Category) license = fields.Embedded(License) defined_type = fields.Integer() published_date = Date() embargo_reason = fields.String() references = fields.Collection(fields.String) class FileShort(Model): id = fields.Integer(required=True) name = fields.String(required=True) size = fields.Integer() class Files(list): def __init__(self, json): list.__init__(self) for a in json: f = FileShort(**a) self.append(f) class FileL1(Model): id = fields.Integer(required=True) name = fields.String(required=True) size = fields.Integer() status = fields.String() viewer_type = fields.String() preview_state = fields.String() preview_meta = fields.Field() is_link_only = fields.Boolean() upload_url = fields.String() upload_token = fields.String() supplied_md5 = fields.String() computed_md5 = fields.String() class Author(Model): id = fields.Integer(required=True) full_name = fields.String(required=True) is_active = fields.Boolean() url_name = fields.String() orcid_id = fields.String() class ArticleVersion(Model): version = fields.Integer() url = fields.String() class ArticleEmbargo(Model): is_embargoed = fields.Integer() embargo_date = Date() embargo_type = fields.String() embargo_reason = fields.String() class ArticleConfidentiality(Model): is_confidential = fields.Boolean() reason = fields.String() class ArticleL2(Model): id = fields.Integer(required=True) title = fields.String(required=True) doi = fields.String(required=True) url = fields.String(required=True) published_date = Date(required=True) citation = fields.String() confidential_reason = fields.String() embargo_type = fields.String() is_confidential = fields.Boolean() size = fields.Integer() funding = fields.String() tags = fields.Collection(fields.String) version = fields.Integer() is_active = fields.Integer() is_metadata_record = fields.Boolean() metadata_reason = fields.String() status = fields.String() description = fields.String() is_embargoed = fields.Boolean() embargo_date = Date() is_public = fields.Boolean() modified_date = Date() created_date = Date() has_linked_file = fields.Boolean() categories = fields.Collection(Category) license = fields.Embedded(License) defined_type = fields.Integer() published_date = Date() embargo_reason = fields.String() references = fields.Collection(fields.String) files = fields.Collection(FileShort) authors = fields.Collection(Author) custom_fields = fields.Collection(CustomField) figshare_url = fields.String() resource_doi = fields.String() resource_name = fields.String() resource_title = fields.String() class ArticleLocation(Model): location = fields.String() class CollectionCreate(Model): title = fields.String(required=True) description = fields.String() # doi = fields.String() articles = fields.Collection(fields.Integer) authors = fields.Collection(AuthorCreate) categories = fields.Collection(fields.Integer) tags = fields.Collection(fields.String) references = fields.Collection(fields.String) # resource_id = fields.String() # resource_doi = fields.String() # resource_link = fields.String() # resource_title = fields.String() # resource_versions = fields.Integer() custom_fields = fields.Field() class CollectionShort(Model): title = fields.String() doi = fields.String() url = fields.String() id = fields.Integer() published_date = Date() class CollectionVersion(Model): version = fields.Integer() url = fields.String() class CollectionL1(Model): title = fields.String() doi = fields.String() url = fields.String() id = fields.Integer() published_date = Date() group_resource_id = fields.String() resource_id = fields.String() resource_doi = fields.String() resource_title = fields.String() resource_version = fields.String() version = fields.Integer() description = fields.String() categories = fields.Collection(Category) references = fields.Collection(fields.String) tags = fields.Collection(fields.String) authors = fields.Collection(Author) institution_id = fields.Integer() group_id = fields.Integer() public = fields.Integer() # custom_metadata = fields.Collection(fields.Field) citation = fields.String() custom_fields = fields.Collection(CustomField) created_date = Date() modified_date = Date() resource_link = fields.String() articles_count = fields.Integer() class ArticleFile(Model): status = fields.String() is_link_only = fields.Boolean() name = fields.String() viewer_type = fields.String() preview_state = fields.String() download_url = fields.String() supplied_md5 = fields.String() computed_md5 = fields.String() upload_token = fields.String() upload_url = fields.String() id = fields.Integer() size = fields.Integer() class ArticleFileUploadPart(Model): partNo = fields.Integer() startOffset = fields.Integer() endOffset = fields.Integer() status = fields.String() locked = fields.Boolean() class ArticleFileUploadStatus(Model): token = fields.String() md5 = fields.String() size = fields.Integer() name = fields.String() status = fields.String() parts = fields.Collection(ArticleFileUploadPart) class FigshareError(Model): message = fields.String() code = fields.String() PKA 5Hzpigshare/input_helpers.pyfrom models import * from pyclist.model_helpers import ask_details_for_type, MODEL_MAP, parse_for_help, edit_details_for_type import caching import booby CATEGORIES_CACHE = {} def create_custom_fields(): result = {} print "Enter custom field key/value pairs. Once finished, press enter when asked for a key." print while True: key = raw_input(" - custom field key (String): ") if parse_for_help(key, custom_fields_help): continue if not key: break value = raw_input( " - custom field value for key '{}' (String)': ".format(key)) while not value: print "Value can't be empty." value = raw_input( " - custom field value for key '{}' (String)': ".format(key)) result[key] = value return result def create_author(id_or_name=None): ''' Create an AutorCreate object using an id or name. If no id/name is provided, ask user on the commandline. ''' if not id_or_name: id_or_name = raw_input(" - author id or name (Integer or String): ") if parse_for_help(id_or_name, author_help): return create_author() if not id_or_name: # user is finished return None author = AuthorCreate() try: author.id = int(id_or_name) except: author.name = id_or_name return author def title_help(*args): print "The title for the article." def defined_type_help(*args): print "Article type, one of:" print for k, v in FIGSHARE_DEFINED_TYPES_DICT.iteritems(): print v print def author_help(*args): print "If possible, use the authors id instead of name, that way all articles belonging to the same author are guaranteed to end up associated with the same entity in Figshare." print print "Following is a list of cached names and associated ids. This list is not complete and just used as a workaround because the Figshare API does not allow querying authors directly." print for id, name in caching.get_authors().iteritems(): if args: filter = args[0] if filter.islower(): if filter not in name.lower(): continue else: if filter not in name: continue print "{} - {}".format(id, name) def create_categories_help_func(api): def categories_help(*args): global CATEGORIES_CACHE if args: filter = args[0] else: filter = None if not CATEGORIES_CACHE: CATEGORIES_CACHE = api.call_list_categories() for c in CATEGORIES_CACHE: if filter: if filter.islower(): if filter not in c['title'].lower(): continue else: if filter not in c['title']: continue print "{}. {}".format(c['id'], c['title']) return categories_help def create_licenses_help_func(api): def licenses_help(*args): if args: filter = args[0] else: filter = None licenses = api.call_list_licenses() for c in licenses: if filter: if filter.islower(): if filter not in c['title'].lower(): continue else: if filter not in c['title']: continue print "{}. {} ({})".format(c.value, c.name, c.url) return licenses_help def create_articles_help_func(api): def articles_help(*args): if args: filter = args[0] articles = api.call_search_articles(filter) else: articles = api.call_list_articles() for a in articles: print u"{} - {}".format(a.id, a.title) return articles_help def custom_fields_help(): print "Custom metadata fields." def create_article_help_map(api): help_map = {} help_map['title'] = title_help help_map['categories'] = create_categories_help_func(api) help_map['authors'] = author_help help_map['defined_type'] = defined_type_help help_map['license'] = create_licenses_help_func(api) help_map['custom_fields'] = custom_fields_help return help_map def create_collection_help_map(api=None): help_map = {} help_map['title'] = title_help help_map['categories'] = create_categories_help_func(api) help_map['authors'] = author_help help_map['articles'] = create_articles_help_func(api) return help_map def create_collection(details=None, api=None): if not details: help_map = create_collection_help_map(api=api) collection = ask_details_for_type(CollectionCreate, False, help_map) elif isinstance(details, dict): collection = CollectionCreate(**details) elif isinstance(details, basestring): collection = CollectionCreate(**(json.loads(details))) else: raise Exception("Can't convert to CollectionCreate object.") return collection def create_article(details=None, api=None): if not details: help_map = create_article_help_map(api) article = ask_details_for_type(ArticleCreate, False, help_map) elif isinstance(details, dict): article = ArticleCreate(**details) elif isinstance(details, basestring): article = ArticleCreate(**(json.loads(details))) else: raise Exception("Can't convert to ArticleCreate object.") return article def edit_collection(id, api=None): # load current collection old = api.call_read_my_collection(id) help_map = create_collection_help_map(api) return edit_details_for_type(CollectionCreate, old, help_map) def edit_article(id, api=None): # load current article old = api.call_read_my_article(id) help_map = create_article_help_map(api) return edit_details_for_type(ArticleCreate, old, help_map) MODEL_MAP[AuthorCreate] = create_author MODEL_MAP[booby.fields.Field] = create_custom_fields PK;HQ"Xpigshare/admin_api.pyfrom restkit import Resource, request from api import FIGSHARE_BASE_URL, get_headers try: import simplejson as json except ImportError: import json # py2.6 only class figshare_admin_api(Resource): def __init__(self, url=FIGSHARE_BASE_URL, token=None, verbose=False, **kwargs): self.url = url self.token = token self.verbose = verbose super(figshare_admin_api, self).__init__(self.url) def call_hr_feed(self, feed_file): ''' Upload updated feed file. :type feed_file: str :param feed_file: the path to the feed file :return: whether the import was successful :rtype: bool ''' with open(feed_file, 'rb') as fin: files = {'hrfeed': (feed_file, fin)} response = self.post('/institution/hrfeed/upload', files=files, headers=get_headers(token=self.token)) print(response.content) PKݼH|MaXXpigshare/api.pyfrom models import * from restkit import Resource, request import inspect import hashlib import os import caching from input_helpers import create_article, create_collection, edit_article, edit_collection try: import simplejson as json except ImportError: import json # py2.6 only FIGSHARE_BASE_URL = 'https://api.figshare.com/v2' DEFAULT_LIMIT = 1000 API_ARG_MAP = {'read_my_article': 'id', 'read_my_collection': 'id', 'add_article': 'article_ids', 'publish_article': 'id', 'read_article': 'id', 'read_collection': 'id', 'read_collection_articles': 'id', 'read_my_collection_articles': 'id', 'remove_article': 'article_id', 'search_articles': 'search_term', 'search_collections': 'search_term', 'upload_new_file': 'file', 'delete_article': 'article_id'} # Helper methods ======================================== def get_headers(token=None): headers = {} headers['Content-Type'] = 'application/json' if token: headers['Authorization'] = 'token ' + token return headers def get_request_params(params={}, limit=DEFAULT_LIMIT): params['limit'] = limit return params def create_fileupload_dict(fname): ''' Creates the dict necessary for a file upload ( https://github.com/figshare/user_documentation/blob/master/APIv2/articles.md#initiate-new-file-upload-within-the-article ) ''' result = {} hash = hashlib.md5() with open(fname, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash.update(chunk) result['md5'] = hash.hexdigest() result['name'] = os.path.basename(fname) result['size'] = os.path.getsize(fname) return result def multiply_call(function_to_call, list_of_ids): ''' If a method is called with multiple ids, it can use this method to create a list of results, using all ids. ''' result = [] for id in list_of_ids: r = function_to_call(id) result.append(r) return result API_MARKER = 'call' def is_api_method(field): return inspect.ismethod(field) and field.__name__.startswith(API_MARKER) # API-Wrapper classes =================================== class figshare_api(Resource): def __init__(self, url=FIGSHARE_BASE_URL, token=None, verbose=False, **kwargs): self.url = url self.token = token self.verbose = verbose super(figshare_api, self).__init__(self.url) def call_create_json(self, model): ''' Creates a json string by interactively asking the user questions about field values. :type model: str :param model: the model type to create json for (one of: article, collection) :return: the initiated model :rtype: Model ''' if model == 'article': result = create_article(api=self) elif model == 'collection': result = create_collection(api=self) else: raise Exception("Model type '{}' not supported".format(model)) print print "Result json for {}:".format(model) print return result def call_list_articles(self): '''Returns a list of all articles. :return: A list of articles matching the search term. :rtype: list ''' response = self.get('/articles', params_dict=get_request_params()) articles_json = json.loads(response.body_string()) result = [] for a in articles_json: art = ArticleShort(**a) result.append(art) return result def call_search_articles(self, search_term): ''' Searches for an article, returns a list of all matches. :type search_term: str :param search_term: the term to search for :return: A list of articles matching the search term. :rtype: list ''' data = get_request_params() data['search_for'] = search_term payload = json.dumps(data) response = self.post('/articles/search', payload=payload) articles_json = json.loads(response.body_string()) result = [] for a in articles_json: art = ArticleShort(**a) result.append(art) return result def call_read_article(self, id): ''' Read an articles. :type id: int :param id: the article id :return: the article details :rtype: ArticleL2 ''' response = self.get('/articles/{}'.format(id), headers=get_headers(token=self.token)) article_dict = json.loads(response.body_string()) # print article_dict article = ArticleL2(**article_dict) # cache authors for au in article.authors: caching.add_author(au.id, au.full_name) return article def call_list_my_articles(self): ''' Returns a list of of your own articles. :return: A list of articles. :rtype: Articles ''' response = self.get('/account/articles', headers=get_headers( token=self.token), params_dict=get_request_params()) articles_json = json.loads(response.body_string()) result = [] for a in articles_json: art = ArticleShort(**a) result.append(art) return result def call_search_my_articles(self, search_term): ''' Searches within your own articles. :type search_term: str :param search_term: the term to search for :return: A list of articles matching the search term. :rtype: Articles ''' data = get_request_params() data['search_for'] = search_term # payload = json.dumps(data) payload = data payload = json.dumps(data) response = self.post('/account/articles/search', payload=payload, headers=get_headers(token=self.token)) articles_json = json.loads(response.body_string()) result = [] for a in articles_json: art = ArticleShort(**a) result.append(art) return result def call_create_article(self, article=None): ''' Upload a new article. :type article: ArticleCreate :param article: the article to create :return: whether the creation process was successful :rtype: bool ''' if not article: article = create_article(api=self) payload = article.to_json() if self.verbose: print print "--------------------" print "Generated json:" print print payload print "--------------------" print response = self.post(path='/account/articles', payload=payload, headers=get_headers(token=self.token)) loc = ArticleLocation(**json.loads(response.body_string())) return loc def call_update_article(self, id, article): ''' Update an article. :type id: int :param id: the id of the article to udpate :type article: str :param article: the article details to update :return: the link to the article :rtype: str ''' if not article: article_dict = edit_article(id, api=self) else: article_dict = json.loads(article) payload = json.dumps(article_dict) if self.verbose: print print "--------------------" print "Generated json:" print print payload print "--------------------" print try: response = self.put('/account/articles/{}'.format(id), headers=get_headers(token=self.token), payload=payload) except Exception as e: print e return False return self.call_read_my_article(id) def call_upload_new_file(self, id, file): ''' Upload a file and associate it with an article. :type id: int :param id: the id of the article :type file: str :param file: the file to upload :return: the upload location :rtype: ArticleLocation ''' payload = json.dumps(create_fileupload_dict(file)) response = self.post('/account/articles/{}/files'.format(id), headers=get_headers(token=self.token), payload=payload) loc = ArticleLocation(**json.loads(response.body_string())) response = request(loc.location, headers=get_headers(token=self.token)) article_file = ArticleFile(**json.loads(response.body_string())) # upload_url = '{0}/{1}'.format(article_file.upload_url, # article_file.upload_token) upload_url = article_file.upload_url response = request(upload_url) article_file_upload_status = ArticleFileUploadStatus( **json.loads(response.body_string())) with open(file, 'rb') as file_input: for part in article_file_upload_status.parts: size = part['endOffset'] - part['startOffset'] + 1 response = request( '{0}/{1}'.format(upload_url, part.partNo), method='PUT', body=file_input.read(size)) response = request(loc.location, method='POST', headers=get_headers(token=self.token)) return loc def call_read_my_article(self, id): ''' Read one of your articles. :type id: int :param id: the article id :return: the article details :rtype: ArticleL2 ''' response = self.get('/account/articles/{}'.format(id), headers=get_headers(token=self.token)) article_dict = json.loads(response.body_string()) # print article_dict article = ArticleL2(**article_dict) # cache authors for au in article.authors: caching.add_author(au.id, au.full_name) return article def call_list_my_article_files(self, id): ''' List all files associated with one of your articles. :type id: int :param id: the article id :return: a list of files :rtype: FileShort ''' response = self.get('/account/articles/{}/files'.format(id), headers=get_headers(token=self.token)) file_json = json.loads(response.body_string()) result = [] for f in file_json: fi = FileShort(**f) result.append(fi) return result def call_publish_article(self, id): ''' Publish an article. :type id: int :param id: the article id :return: the link to the article :rtype: str ''' response = self.post( '/account/articles/{}/publish'.format(id), headers=get_headers(token=self.token)) loc = ArticleLocation(**json.loads(response.body_string())) return loc def call_publish_collection(self, id): ''' Publish a collection. :type id: int :param id: the collection id :return: the link to the collection :rtype: str ''' response = self.post( '/account/collections/{}/publish'.format(id), headers=get_headers(token=self.token)) loc = ArticleLocation(**json.loads(response.body_string())) return loc def call_list_collections(self): ''' Lists all publicly available collections. :return: all collections :rtype: Collections ''' response = self.get('/collections', params_dict=get_request_params()) collections_json = json.loads(response.body_string()) result = [] for c in collections_json: col = CollectionShort(**c) result.append(col) return result def call_search_collections(self, search_term): ''' Searches for a collection, returns a list of all matches. :type search_term: str :param search_term: the term to search for :return: A list of collections matching the search term. :rtype: Collections ''' data = get_request_params() data['search_for'] = search_term payload = json.dumps(data) response = self.post('/collections/search', payload=payload) collections_json = json.loads(response.body_string()) result = [] for c in collections_json: col = CollectionShort(**c) result.append(col) return result def call_read_collection(self, id): ''' Reads the collection with the specified id. :type id: int :param id: the collection id :return: the collection details :rtype: CollectionL1 ''' response = self.get('/collections/{}'.format(id)) col_dict = json.loads(response.body_string()) col = CollectionL1(**col_dict) # author caching for au in col.authors: caching.add_author(au.id, au.full_name) return col def call_read_collection_articles(self, id): ''' Lists all articles of a collection. :type id: int :param id: the collection id :return: a list of articles :rtype: list ''' response = self.get('/collections/{}/articles'.format(id), params_dict=get_request_params()) articles_json = json.loads(response.body_string()) result = [] for a in articles_json: art = ArticleShort(**a) result.append(art) return result def call_list_my_collections(self): ''' Lists all publicly available collections. :return: all collections :rtype: Collections ''' response = self.get('/account/collections', params_dict=get_request_params(), headers=get_headers(token=self.token)) collections_json = json.loads(response.body_string()) result = [] for c in collections_json: col = CollectionShort(**c) result.append(col) return result def call_read_my_collection(self, id): ''' Reads the collection with the specified id. :type id: int :param id: the collection id :return: the collection details :rtype: CollectionL1 ''' response = self.get('/account/collections/{}'.format(id), headers=get_headers(token=self.token)) col_dict = json.loads(response.body_string()) col = CollectionL1(**col_dict) print col # author caching for au in col.authors: caching.add_author(au.id, au.full_name) return col def call_read_my_collection_articles(self, id): ''' Lists all articles of a collection. :type id: int :param id: the collection id :return: a list of articles :rtype: Articles ''' response = self.get('/account/collections/{}/articles'.format(id), headers=get_headers(token=self.token), params_dict=get_request_params()) articles_json = json.loads(response.body_string()) result = [] for a in articles_json: art = ArticleShort(**a) result.append(art) return result def call_create_collection(self, collection=None): ''' Create a new collection using the provided article_ids :type collection: CollectionCreate :param collection: the collection to create :return: the link to the new collection :rtype: str ''' if not collection: collection = create_collection(api=self) payload = collection.to_json() if self.verbose: print print "--------------------" print "Generated json:" print print payload print "--------------------" print response = self.post( '/account/collections', headers=get_headers(token=self.token), payload=payload) loc = ArticleLocation(**json.loads(response.body_string())) return loc def call_update_collection(self, id, collection): ''' Update a collection. :type id: int :param id: the id of the collection to update :type collection: str :param collection: the collection to create :return: the link to the new collection :rtype: str ''' if not collection: collection_dict = edit_collection(id, api=self) else: collection_dict = json.loads(collection) payload = json.dumps(collection_dict) if self.verbose: print print "--------------------" print "Generated json:" print print payload print "--------------------" print try: response = self.put('/account/collections/{}'.format(id), headers=get_headers(token=self.token), payload=payload) except Exception as e: print e return False # print "XXX"+str(response.body_string()) return self.call_read_my_collection(id) def call_add_article(self, id, article_ids): ''' Adds one or more articles to a collection. :type id: int :param id: the id of the collection :type article_ids: list :param article_ids: one or more article ids :return: whether the operation succeeded :rtype: bool ''' if isinstance(article_ids, (int, long)): article_ids = [article_ids] # convert to ints article_ids = [int(x) for x in article_ids] if len(article_ids) > 10: raise Exception("No more than 10 articles allowed.") payload = {} payload['articles'] = article_ids payload = json.dumps(payload) try: response = self.post('/account/collections/{}/articles'.format(id), headers=get_headers(token=self.token), payload=payload) except Exception as e: print e return {"success": False} return {"success": True} def call_replace_articles(self, collection_id, article_ids): ''' Replace all articles of a collection. :type collection_id: int :param collection_id: the id of the collection :type article_ids: list :param article_ids: a list of one or more article ids :return: whether the operation succeeded :rtype: bool ''' if len(article_ids) > 10: raise Exception("No more than 10 articles allowed.") payload = {} payload['articles'] = article_ids payload = json.dumps(payload) try: response = self.put('/account/collections/{}/articles'.format( collection_id), headers=get_headers(token=self.token), payload=payload) except Exception as e: print e return {"success": False} return {"success": True} def call_delete_article(self, article_id): ''' Deletes an article (that is not published yet). :type article_id: int :param article_id: the id of the article to delete :return: whether the operation succeeded :rtype: bool ''' try: response = self.delete('/account/articles/{}'.format( article_id), headers=get_headers(token=self.token)) except Exception as e: print e return {"success": False} return {"success": True} def call_remove_article(self, collection_id, article_id): ''' Removes one or more articles from a collection. :type collection_id: int :param collection_id: the id of the collection :type article_id: int :param article_id: the article to remove :return: whether the operation succeeded :rtype: bool ''' try: response = self.delete('/account/collections/{}/articles/{}'.format( collection_id, article_id), headers=get_headers(token=self.token)) except Exception as e: print e return {"success": False} return {"success": True} def call_list_categories(self, filter=None): ''' Lists all categories. If a filter is provided, it'll filter the results using a simple, case-insensitive string match. If the filter contains at least one uppercase letter, the match is case-sensitive. :type filter: str :param filter: a string to filter the category names :return: a list of all categories :rtype: list ''' response = self.get('/categories') categories_json = json.loads(response.body_string()) result = [] for c in categories_json: if filter: if filter.islower(): if filter not in c['title'].lower(): continue else: if filter not in c['title']: continue col = Category(**c) result.append(col) return result def call_list_licenses(self, filter=None): ''' Lists all licenses. If a filter is provided, it'll filter the results using a simple, case-insensitive string match. If the filter contains at least one uppercase letter, the match is case-sensitive. :type filter: str :param filter: a string to filter the license names :return: a list of all licenses :rtype: list ''' response = self.get('/licenses') licenses_json = json.loads(response.body_string()) result = [] for c in sorted(licenses_json, key=lambda lic: lic['value']): if filter: if filter.islower(): if filter not in c['title'].lower(): continue else: if filter not in c['title']: continue lic = License(**c) result.append(lic) return result PK Hzpigshare/stats_api.pyfrom restkit import Resource, request from api import FIGSHARE_BASE_URL, get_headers try: import simplejson as json except ImportError: import json # py2.6 only STATS_DEFAULT_URL="https://stats.figshare.com/" STATS_TYPES = ["views", "downloads", "shares"] ITEM_TYPES = ["article", "author", "collection"] STATS_API_ID_ARG_MAP = {} def get_headers(token=None): headers = {} headers['Content-Type'] = 'application/json' if token: headers['Authorization'] = 'Basic ' + token return headers def get_request_params(params={}, start_date=None, end_date=None, sub_item=None, sub_item_id=None): if start_date: params["start_date"] = start_date if end_date: params["end_date"] = end_date if sub_item: params["sub_item"] = sub_item if sub_item_id: params["sub_item_id"] = sub_item_id return params def add_totals_method(cls, stats_type, item_type): def totals_method(self, id): if self.institution: response = self.get('/{0}/total/{1}/{2}/{3}'.format(self.institution, stats_type, item_type, id), params_dict={}, headers=get_headers(token=self.token)) else: response = self.get('/total/{0}/{1}/{2}'.format(stats_type, item_type, id), params_dict={}, headers=get_headers(token=self.token)) totals_json = json.loads("{{\"{0}\":{1}}}".format(id, response.body_string())) return totals_json totals_method.__doc__ = ''' Shows {0} stats for {1}. :type id: int :param id: the id of the {1} :return: json-formatted number of {0} :rtype: str '''.format(stats_type, item_type) totals_method.__name__ = 'call_get_total_{0}_{1}'.format(item_type, stats_type) setattr(cls, totals_method.__name__, totals_method) STATS_API_ID_ARG_MAP[totals_method.__name__[5:]] = 'id' def add_timeline_method(cls, stats_type, item_type): def timeline_method(self, id, granularity="total", start_date=None, end_date=None, sub_item=None, sub_item_id=None): if not granularity: granularity = "total" if self.institution: response = self.get('/{0}/timeline/{1}/{2}/{3}/{4}'.format(self.institution, granularity, stats_type, item_type, id), headers=get_headers(token=self.token), params_dict=get_request_params(start_date=start_date, end_date=end_date, sub_item=sub_item, sub_item_id=sub_item_id)) else: response = self.get('/timeline/{0}/{1}/{2}/{3}'.format(granularity, stats_type, item_type, id), headers=get_headers(token=self.token), params_dict=get_request_params(start_date=start_date, end_date=end_date, sub_item=sub_item, sub_item_id=sub_item_id)) timeline_json = json.loads("{{\"{0}\":{1}}}".format(id, response.body_string())) return timeline_json timeline_method.__doc__ = ''' Shows {0} timeline stats for {1}. :type id: int :param id: the id of the {1} :type granularity: str :param granularity: One of 'year', 'month', 'day', or 'total' (default) :type start_date: str :param start_date: Start date (format: yyyy-mm-dd) :type end_date: str :param end_date: End date (format: yyyy-mm-dd) :type sub_item: str :param sub_item: Can be one of 'category' and 'item_type'. Acts as a filter on the result. :type sub_item_id: int :param sub_item_id: Required if sub_item is also specified. :return: json-formatted timeline of {0} :rtype: str '''.format(stats_type, item_type) timeline_method.__name__ = 'call_get_timeline_{0}_{1}'.format(item_type, stats_type) setattr(cls, timeline_method.__name__, timeline_method) STATS_API_ID_ARG_MAP[timeline_method.__name__[5:]] = 'id' def add_breakdown_method(cls, stats_type, item_type): def breakdown_method(self, id, granularity="total", start_date=None, end_date=None, sub_item=None, sub_item_id=None): params_dict=get_request_params(start_date=start_date, end_date=end_date, sub_item=sub_item_id, sub_item_id=sub_item_id) if not granularity: granularity = "total" if self.institution: response = self.get('/{0}/breakdown/{1}/{2}/{3}/{4}'.format(self.institution, granularity, stats_type, item_type, id), headers=get_headers(token=self.token), params_dict=params_dict) else: response = self.get('/breakdown/{0}/{1}/{2}/{3}'.format(granularity, stats_type, item_type, id), headers=get_headers(token=self.token), params_dict=params_dict) breakdown_json = json.loads("{{\"{0}\":{1}}}".format(id, response.body_string())) return breakdown_json breakdown_method.__doc__ = ''' Shows {0} breakdown stats for {1}. :type id: int :param id: the id of the {1} :type granularity: str :param granularity: One of 'year', 'month', 'day', or 'total' (default) :type start_date: str :param start_date: Start date (format: yyyy-mm-dd) :type end_date: str :param end_date: End date (format: yyyy-mm-dd) :type sub_item: str :param sub_item: Can be one of 'category' and 'item_type'. Acts as a filter on the result. :type sub_item_id: int :param sub_item_id: Required if sub_item is also specified. :return: json-formatted breakdown of {0} :rtype: str '''.format(stats_type, item_type) breakdown_method.__name__ = 'call_get_breakdown_{0}_{1}'.format(item_type, stats_type) setattr(cls, breakdown_method.__name__, breakdown_method) STATS_API_ID_ARG_MAP[breakdown_method.__name__[5:]] = 'id' class figshare_stats_api(Resource): def __init__(self, stats_url=STATS_DEFAULT_URL, institution=None, stats_token=None, verbose=False, **kwargs): self.url = stats_url self.token = stats_token self.institution = institution self.verbose = verbose super(figshare_stats_api, self).__init__(self.url) for s in STATS_TYPES: for t in ITEM_TYPES: add_totals_method(figshare_stats_api, s, t) add_timeline_method(figshare_stats_api, s, t) add_breakdown_method(figshare_stats_api, s, t) PK Hpigshare/__init__.py# -*- coding: utf-8 -*- """Python client for figshare""" __version__ = '0.5.0' __url__ = 'https://github.com/makkus/pigshare' __author__ = 'Markus Binsteiner' __email__ = 'makkus@gmail.com' PK!H^- (pigshare-0.5.0.dist-info/DESCRIPTION.rstUNKNOWN PK!Ho44)pigshare-0.5.0.dist-info/entry_points.txt[console_scripts] pigshare = pigshare.pigshare:run PK!HP?E&pigshare-0.5.0.dist-info/metadata.json{"classifiers": ["Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Natural Language :: English", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4"], "extensions": {"python.commands": {"wrap_console": {"pigshare": "pigshare.pigshare:run"}}, "python.details": {"contacts": [{"email": "makkus@gmail.com", "name": "Markus Binsteiner", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/makkus/pigshare"}}, "python.exports": {"console_scripts": {"pigshare": "pigshare.pigshare:run"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["pigshare", "figshare", "client", "rest", "api"], "license": "GPLv3", "metadata_version": "2.0", "name": "pigshare", "run_requires": [{"requires": ["argcomplete", "argparse", "booby", "parinx", "pyclist", "restkit", "setuptools", "simplejson"]}], "summary": "Python client for figshare", "test_requires": [{"requires": []}], "version": "0.5.0"}PK!H[ &pigshare-0.5.0.dist-info/top_level.txtpigshare PK!Hndnnpigshare-0.5.0.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PK!H!pigshare-0.5.0.dist-info/METADATAMetadata-Version: 2.0 Name: pigshare Version: 0.5.0 Summary: Python client for figshare Home-page: https://github.com/makkus/pigshare Author: Markus Binsteiner Author-email: makkus@gmail.com License: GPLv3 Keywords: pigshare figshare client rest api Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Requires-Dist: argcomplete Requires-Dist: argparse Requires-Dist: booby Requires-Dist: parinx Requires-Dist: pyclist Requires-Dist: restkit Requires-Dist: setuptools Requires-Dist: simplejson UNKNOWN PK!HZ)iZZpigshare-0.5.0.dist-info/RECORDpigshare/__init__.py,sha256=K1UjnnYbTLQg4nVptUdcGaxeW8lorZP3FaZlC75Qbrg,193 pigshare/admin_api.py,sha256=l5ypnWXXSEPNUj9eAdUOuLjY6zrMZi__i3V9u4jqQ-s,965 pigshare/api.py,sha256=E8pdu6PHtThNRZI6tw7rvsiZgDTgV_s9nRHm-Csh13Y,22744 pigshare/caching.py,sha256=rDR1QaCOv5KKMNCRmKE8IIRom17V5lCbDmYUbl1G9ZU,602 pigshare/example.py,sha256=lVC32Xj3dQP0o37wT_AaDGjGuyOVqJSj9SrGS7Jj0Pw,4250 pigshare/helpers.py,sha256=YhIZLQnNjSBZZKK6kc-WRaddbWUpy1MoIe77MPbm2d4,1793 pigshare/input_helpers.py,sha256=6MPorOZUOudF5LWxl_UIfwDdroRNeQ6RkT-XwOHazRc,6161 pigshare/models.py,sha256=O5YERV2P0_tliDRSVFwK6m3ZdGy5pLRIAMdjuAQ-u4k,9739 pigshare/pigshare.py,sha256=dJPN350KpCU_NkxuVKjwVdyNIxVMC_ByT7ZD6HMMrao,4116 pigshare/stats_api.py,sha256=HXWkzsCIqnjAI3IX5f9VAflOIBbyInwx0rVvEyIj6js,6086 pigshare-0.5.0.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10 pigshare-0.5.0.dist-info/METADATA,sha256=0gZcGHdPfXvFkW9dRCEPbVHen0pialUxBsaJsFOSiyQ,1010 pigshare-0.5.0.dist-info/RECORD,, pigshare-0.5.0.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 pigshare-0.5.0.dist-info/entry_points.txt,sha256=9zalmllY8mnajD4P53q8WPabNz6qbMPgJyNufisjFPI,52 pigshare-0.5.0.dist-info/metadata.json,sha256=RFdN00SpZeiAFO3j5OO89C0Mawg8xnDYNHEHQ-CXwMY,1266 pigshare-0.5.0.dist-info/top_level.txt,sha256=GRevhEnN-_PTYncW_As92pR6WPe4B_cVlt4A3XeKIws,9 PKhGkpigshare/example.pyPKe3HɡDpigshare/helpers.pyPK3HnC~ZZpigshare/caching.pyPKr HNepigshare/pigshare.pyPKHc & &*pigshare/models.pyPKA 5Hz Qpigshare/input_helpers.pyPK;HQ"XQipigshare/admin_api.pyPKݼH|MaXXImpigshare/api.pyPK HzNpigshare/stats_api.pyPK HGpigshare/__init__.pyPK!H^- (:pigshare-0.5.0.dist-info/DESCRIPTION.rstPK!Ho44)pigshare-0.5.0.dist-info/entry_points.txtPK!HP?E&pigshare-0.5.0.dist-info/metadata.jsonPK!H[ &;pigshare-0.5.0.dist-info/top_level.txtPK!Hndnnpigshare-0.5.0.dist-info/WHEELPK!H!2pigshare-0.5.0.dist-info/METADATAPK!HZ)iZZcpigshare-0.5.0.dist-info/RECORDPK