PKHbj0pigshare/__init__.py# -*- coding: utf-8 -*- """pigshare.__main__: executed when pyroji directory is called as script.""" __version__ = '0.5.6' __url__ = 'https://github.com/makkus/pigshare' 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) PKHN8(((pigshare-0.4.0.dist-info/DESCRIPTION.rst================= Markus Binsteiner ================= :Author: Markus Binsteiner .. contents:: 1 Pigshare ---------- Python client library and commandline tool for institutional Figshare. The commandline options are created dynamically from the available api-method wrapper code, which is why some of them might feel a bit clumsy. Also, many of the commands only support values as json-formatted strings. I might change that in the future, but it'd require more complex cli-argparse creation code and I'm not sure whether it's worth it. 1.1 Notes ~~~~~~~~~ So far, I've only tested this on Linux. 1.2 Requirements ~~~~~~~~~~~~~~~~ - python-dev package (for simplejson compilation I think) - argparse - setuptools - restkit - booby - simplejson - parinx - pyclist - argcomplete 1.3 Installation ~~~~~~~~~~~~~~~~ 1.3.1 Release ^^^^^^^^^^^^^ :: (sudo) pip install pigshare 1.3.2 Development version from Github ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: (sudo) pip install https://github.com/UoA-eResearch/pigshare/archive/master.zip 1.4 Usage (commandline client) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.4.1 Connection details ^^^^^^^^^^^^^^^^^^^^^^^^ *pigshare* reads a config file $HOME/.pigshare.conf, in the format: :: [default] url = https://api.figsh.com/v2 token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (note, this example uses the staging environment) *pigshare* supports profiles, so you can have multiple profiles in your config file like for example: :: [default] url = https://api.figsh.com/v2 token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [markus] url = https://api.figshare.com/v2 token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [not_markus_but_somebody_else] url = https://api.figshare.com/v2 token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Now, when you call *pigshare* with the **-p** argument, you can switch between different backends/identities: :: pigshare -p markus [command] The command you chose will be using the selected connection parameters. 1.4.2 Features: ^^^^^^^^^^^^^^^ 1.4.2.1 Supported ::::::::::::::::: - creation of articles, via a json string or interactively - listing of public and private articles - searching for public and private articles - updating of articles - creation of collections, via a json string or interactively - listing of public and private collections - searching for public and private collections - updating of collections - listing of categories and their ids - listing of licenses and their ids - publishing of articles and collections 1.4.2.2 Not (yet) supported ::::::::::::::::::::::::::: - queries with more than 1000 results, only the first 1000 results are displayed - automatically deal with the 10 item limits on some methods - everything else 1.4.3 General usage ^^^^^^^^^^^^^^^^^^^ Basic usage is displayed via: :: pigshare -h Command specific usage can be displayed via: :: pigshare [command] -h 1.4.4 Interactive input ^^^^^^^^^^^^^^^^^^^^^^^ Some of the commands offer interactive input (e.g. create\ :sub:`article`\, edit\ :sub:`article`\, create\ :sub:`collection`\, ...). If you choose to use that, you can get help on any particular field by typing '?' as value. Some fields support a more advanced help functionality: - **categories**: '?' lists all available categories along with their internal figshare id (which you need to provide as input), '? [search\ :sub:`term`\]' lets you filter this list with the provided search term - **authors**: '?' lists all authors and their internal ids (always use the latter if you know it) that *pigshare* knows about (authors that came up in past queries, so this is not a comprehensible list, if you can't find the author you want, try to find it via the web-interface) - **licenses**: '?' lists all licenses and their id, '? search\ :sub:`term`\' filters the result - **defined\ :sub:`type`\**: '?' lists the available and valid article types Some fields support multiple values (list input). If that's the case, *pigshare* will tell you about it, and let you input the single items one after another. Once you are finished, just press 'enter' on an empty field. 1.4.5 Filtering of output fields ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (Sub-)commands that display one or more items can be called using an output filter (the **-o** argument before the sub-command). Depending on the sub-command called only certain fields of the items are available (e.g. **list\ :sub:`articles`\** has only a subset of fields compared to **read\ :sub:`article`\**). I'd recommend trying out the command you want to run first, and checking which fields are available, then run the command again with the appropriate filter. A command to list all articles and only display the **doi** and **title** of each article would be: :: pigshare -o doi,title list_articles For more advanced filtering, consider piping in the 'full' output of *pigshare* into a tool like jq ( `https://stedolan.github.io/jq/ `_ ). 1.4.6 Commonly used commands ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1.4.6.1 Articles :::::::::::::::: 1.4.6.1.1 List articles ''''''''''''''''''''''' To list all articles and display the **doi**, (internal) **id**, **title**, **url**, and **published\ :sub:`date`\** for each, issue: :: pigshare list_articles To display a table with all articles, but only display **doi** and **title**, you can use: :: pigshare -o doi,title list_articles 1.4.6.1.2 Read an article ''''''''''''''''''''''''' To display the properties of an article, use: :: pigshare read_article [article_id] To display the doi and all tags of a number of articles, use (tags are not part of the 'short' article format that the **list\ :sub:`articles`\** command returns): :: pigshare -o doi,tags read_article [article_id] [article_id] [article_id] 1.4.6.1.3 Search for articles ''''''''''''''''''''''''''''' To list all articles matching a search string, issue: :: pigshare search_articles --search_term [search_term] To display all dois and titles of articles that match a search string: :: pigshare -o doi,title search_articles --search_term [search_term] 1.4.6.1.4 List my articles '''''''''''''''''''''''''' To list all of your own articles: :: pigshare list_my_articles 1.4.6.1.5 To create a new article ''''''''''''''''''''''''''''''''' :: pigshare create_article --article '{"title": "Markus test", "custom_fields": {"key1": "value"}}' Or, if you want *pigshare* to ask your input for every one of the fields: :: pigshare create_article 1.4.6.1.6 Upload one (or more files) for an article ''''''''''''''''''''''''''''''''''''''''''''''''''' :: pigshare upload_new_file --id [article_id] file1 [file2 ... ...] 1.4.6.2 Collections ::::::::::::::::::: Very similar to articles. 1.4.7 Workflows ^^^^^^^^^^^^^^^ 1.4.7.1 Reorder articles in collections ::::::::::::::::::::::::::::::::::::::: Because of how Figshare works at the moment (collections are sorted by timestamp), the easiest way to change the order of articles within a collection is to 're-publish' an already published article. Usually that doesn't result in a new DOI for that article (which would be bad). So, if you want to order the articles alphabetically for example, you could do it this way: - first, find the list of article ids :: $ pigshare -o title,id -p martin search_my_articles --search_term ISSP ISSP1991: Religion I 2000910 ISSP1992: Social Inequality II 2000913 ISSP1993: Environment I 2000916 ISSP1994: Family and Changing Gender Roles II 2000919 ISSP1995: National Identity I 2000922 ISSP1996: Role of Government III 2000925 ISSP1997: Work Orientations II 2000928 ISSP1998: Religion II 2000934 ISSP1999: Social Inequality III 2000937 ISSP2000: Environment II 2000940 ISSP2001: Social Networks II 2000943 ISSP2002: Family and Changing Gender Roles III 2000946 ISSP2003: National Identity II 2000949 ISSP2004: Citizenship I 2000952 ISSP2005: Work Orientations III 2000955 ISSP2006: Role of Government IV 2000958 ISSP2007: Leisure Time and Sports I 2000961 ISSP2008: Religion III 2000964 ISSP2009: Social Inequality IV 2000967 ISSP2010: Environment III 2000970 - then, 'touch' (publish) the articles in the right (reverse) order :: pigshare -p martin publish_article 2000970 2000967 2000964 2000961 2000958 2000955 2000952 2000949 2000946 2000943 2000940 2000937 2000934 2000928 2000925 2000922 2000919 2000916 2000913 2000910 - check the webfrontend whether it worked by refreshing the collections page Be aware that if an article got a new version since it was added to a collection, the old version of the article is included in it. If you want the new version, you need to manually remove and re-add the article before you do anything else. 1.4.8 Other random example calls: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: # create new collection pigshare create_collection --collection '{"title": "Collection markus test", "articles": [2009074,2009075,2009084], "custom_fields": {"test1": "value1"}}' :: # add articles to a collection pigshare add_article --id 2761 --article_ids [2009103,2009106] :: # search all my articles that contain a search_term, display only ids, separated by ',' (useful to copy and paste into 'add_article' command) pigshare -o id -s ',' search_my_articles --search_term [search_term] :: # list all of your personal articles, and add all of them to a collection for id in `pigshare -o id list_my_articles`; do echo "$id"; pigshare add_article --collection_id 3222 --article_id "$id"; done :: # update/overwrite the title and articles connected to a collection pigshare update_collection --id 2761 --collection '{"title": "Collection markus test changed", "articles": [2009074,2009075]}' :: # update/overwrite the categories field in a collection pigshare update_article --id 2000077 --article '{"categories": [2]}' :: # update/overwrite the custom_fields of a collection pigshare update_article --id 2000077 --article '{"custom_fields": {"field1":"value1"}}' 1.5 Usage (Library) ~~~~~~~~~~~~~~~~~~~ TODO History ------- 0.1.0 (2015-01-11) --------------------- * First release on PyPI. PKHo44)pigshare-0.4.0.dist-info/entry_points.txt[console_scripts] pigshare = pigshare.pigshare:run PKHiy&pigshare-0.4.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/UoA-eResearch/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.4.0"}PKH[ &pigshare-0.4.0.dist-info/top_level.txtpigshare PKHndnnpigshare-0.4.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 PKHqdI,,!pigshare-0.4.0.dist-info/METADATAMetadata-Version: 2.0 Name: pigshare Version: 0.4.0 Summary: Python client for Figshare Home-page: https://github.com/UoA-eResearch/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 ================= Markus Binsteiner ================= :Author: Markus Binsteiner .. contents:: 1 Pigshare ---------- Python client library and commandline tool for institutional Figshare. The commandline options are created dynamically from the available api-method wrapper code, which is why some of them might feel a bit clumsy. Also, many of the commands only support values as json-formatted strings. I might change that in the future, but it'd require more complex cli-argparse creation code and I'm not sure whether it's worth it. 1.1 Notes ~~~~~~~~~ So far, I've only tested this on Linux. 1.2 Requirements ~~~~~~~~~~~~~~~~ - python-dev package (for simplejson compilation I think) - argparse - setuptools - restkit - booby - simplejson - parinx - pyclist - argcomplete 1.3 Installation ~~~~~~~~~~~~~~~~ 1.3.1 Release ^^^^^^^^^^^^^ :: (sudo) pip install pigshare 1.3.2 Development version from Github ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: (sudo) pip install https://github.com/UoA-eResearch/pigshare/archive/master.zip 1.4 Usage (commandline client) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.4.1 Connection details ^^^^^^^^^^^^^^^^^^^^^^^^ *pigshare* reads a config file $HOME/.pigshare.conf, in the format: :: [default] url = https://api.figsh.com/v2 token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (note, this example uses the staging environment) *pigshare* supports profiles, so you can have multiple profiles in your config file like for example: :: [default] url = https://api.figsh.com/v2 token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [markus] url = https://api.figshare.com/v2 token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [not_markus_but_somebody_else] url = https://api.figshare.com/v2 token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Now, when you call *pigshare* with the **-p** argument, you can switch between different backends/identities: :: pigshare -p markus [command] The command you chose will be using the selected connection parameters. 1.4.2 Features: ^^^^^^^^^^^^^^^ 1.4.2.1 Supported ::::::::::::::::: - creation of articles, via a json string or interactively - listing of public and private articles - searching for public and private articles - updating of articles - creation of collections, via a json string or interactively - listing of public and private collections - searching for public and private collections - updating of collections - listing of categories and their ids - listing of licenses and their ids - publishing of articles and collections 1.4.2.2 Not (yet) supported ::::::::::::::::::::::::::: - queries with more than 1000 results, only the first 1000 results are displayed - automatically deal with the 10 item limits on some methods - everything else 1.4.3 General usage ^^^^^^^^^^^^^^^^^^^ Basic usage is displayed via: :: pigshare -h Command specific usage can be displayed via: :: pigshare [command] -h 1.4.4 Interactive input ^^^^^^^^^^^^^^^^^^^^^^^ Some of the commands offer interactive input (e.g. create\ :sub:`article`\, edit\ :sub:`article`\, create\ :sub:`collection`\, ...). If you choose to use that, you can get help on any particular field by typing '?' as value. Some fields support a more advanced help functionality: - **categories**: '?' lists all available categories along with their internal figshare id (which you need to provide as input), '? [search\ :sub:`term`\]' lets you filter this list with the provided search term - **authors**: '?' lists all authors and their internal ids (always use the latter if you know it) that *pigshare* knows about (authors that came up in past queries, so this is not a comprehensible list, if you can't find the author you want, try to find it via the web-interface) - **licenses**: '?' lists all licenses and their id, '? search\ :sub:`term`\' filters the result - **defined\ :sub:`type`\**: '?' lists the available and valid article types Some fields support multiple values (list input). If that's the case, *pigshare* will tell you about it, and let you input the single items one after another. Once you are finished, just press 'enter' on an empty field. 1.4.5 Filtering of output fields ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (Sub-)commands that display one or more items can be called using an output filter (the **-o** argument before the sub-command). Depending on the sub-command called only certain fields of the items are available (e.g. **list\ :sub:`articles`\** has only a subset of fields compared to **read\ :sub:`article`\**). I'd recommend trying out the command you want to run first, and checking which fields are available, then run the command again with the appropriate filter. A command to list all articles and only display the **doi** and **title** of each article would be: :: pigshare -o doi,title list_articles For more advanced filtering, consider piping in the 'full' output of *pigshare* into a tool like jq ( `https://stedolan.github.io/jq/ `_ ). 1.4.6 Commonly used commands ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1.4.6.1 Articles :::::::::::::::: 1.4.6.1.1 List articles ''''''''''''''''''''''' To list all articles and display the **doi**, (internal) **id**, **title**, **url**, and **published\ :sub:`date`\** for each, issue: :: pigshare list_articles To display a table with all articles, but only display **doi** and **title**, you can use: :: pigshare -o doi,title list_articles 1.4.6.1.2 Read an article ''''''''''''''''''''''''' To display the properties of an article, use: :: pigshare read_article [article_id] To display the doi and all tags of a number of articles, use (tags are not part of the 'short' article format that the **list\ :sub:`articles`\** command returns): :: pigshare -o doi,tags read_article [article_id] [article_id] [article_id] 1.4.6.1.3 Search for articles ''''''''''''''''''''''''''''' To list all articles matching a search string, issue: :: pigshare search_articles --search_term [search_term] To display all dois and titles of articles that match a search string: :: pigshare -o doi,title search_articles --search_term [search_term] 1.4.6.1.4 List my articles '''''''''''''''''''''''''' To list all of your own articles: :: pigshare list_my_articles 1.4.6.1.5 To create a new article ''''''''''''''''''''''''''''''''' :: pigshare create_article --article '{"title": "Markus test", "custom_fields": {"key1": "value"}}' Or, if you want *pigshare* to ask your input for every one of the fields: :: pigshare create_article 1.4.6.1.6 Upload one (or more files) for an article ''''''''''''''''''''''''''''''''''''''''''''''''''' :: pigshare upload_new_file --id [article_id] file1 [file2 ... ...] 1.4.6.2 Collections ::::::::::::::::::: Very similar to articles. 1.4.7 Workflows ^^^^^^^^^^^^^^^ 1.4.7.1 Reorder articles in collections ::::::::::::::::::::::::::::::::::::::: Because of how Figshare works at the moment (collections are sorted by timestamp), the easiest way to change the order of articles within a collection is to 're-publish' an already published article. Usually that doesn't result in a new DOI for that article (which would be bad). So, if you want to order the articles alphabetically for example, you could do it this way: - first, find the list of article ids :: $ pigshare -o title,id -p martin search_my_articles --search_term ISSP ISSP1991: Religion I 2000910 ISSP1992: Social Inequality II 2000913 ISSP1993: Environment I 2000916 ISSP1994: Family and Changing Gender Roles II 2000919 ISSP1995: National Identity I 2000922 ISSP1996: Role of Government III 2000925 ISSP1997: Work Orientations II 2000928 ISSP1998: Religion II 2000934 ISSP1999: Social Inequality III 2000937 ISSP2000: Environment II 2000940 ISSP2001: Social Networks II 2000943 ISSP2002: Family and Changing Gender Roles III 2000946 ISSP2003: National Identity II 2000949 ISSP2004: Citizenship I 2000952 ISSP2005: Work Orientations III 2000955 ISSP2006: Role of Government IV 2000958 ISSP2007: Leisure Time and Sports I 2000961 ISSP2008: Religion III 2000964 ISSP2009: Social Inequality IV 2000967 ISSP2010: Environment III 2000970 - then, 'touch' (publish) the articles in the right (reverse) order :: pigshare -p martin publish_article 2000970 2000967 2000964 2000961 2000958 2000955 2000952 2000949 2000946 2000943 2000940 2000937 2000934 2000928 2000925 2000922 2000919 2000916 2000913 2000910 - check the webfrontend whether it worked by refreshing the collections page Be aware that if an article got a new version since it was added to a collection, the old version of the article is included in it. If you want the new version, you need to manually remove and re-add the article before you do anything else. 1.4.8 Other random example calls: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: # create new collection pigshare create_collection --collection '{"title": "Collection markus test", "articles": [2009074,2009075,2009084], "custom_fields": {"test1": "value1"}}' :: # add articles to a collection pigshare add_article --id 2761 --article_ids [2009103,2009106] :: # search all my articles that contain a search_term, display only ids, separated by ',' (useful to copy and paste into 'add_article' command) pigshare -o id -s ',' search_my_articles --search_term [search_term] :: # list all of your personal articles, and add all of them to a collection for id in `pigshare -o id list_my_articles`; do echo "$id"; pigshare add_article --collection_id 3222 --article_id "$id"; done :: # update/overwrite the title and articles connected to a collection pigshare update_collection --id 2761 --collection '{"title": "Collection markus test changed", "articles": [2009074,2009075]}' :: # update/overwrite the categories field in a collection pigshare update_article --id 2000077 --article '{"categories": [2]}' :: # update/overwrite the custom_fields of a collection pigshare update_article --id 2000077 --article '{"custom_fields": {"field1":"value1"}}' 1.5 Usage (Library) ~~~~~~~~~~~~~~~~~~~ TODO History ------- 0.1.0 (2015-01-11) --------------------- * First release on PyPI. PKHlN^^pigshare-0.4.0.dist-info/RECORDpigshare/__init__.py,sha256=T_UsVe0ADzBgeptN5-BR2fvfIyMtf1vzWMJklS1auRU,173 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.4.0.dist-info/DESCRIPTION.rst,sha256=7poXeajARDaH5RQV5eiU3lWWDQJkJTM-pspOpfIun6M,10410 pigshare-0.4.0.dist-info/METADATA,sha256=cFZZwKCQuk8kO2RMZYSr6wKcRQM_kfDlrHjb1Fv_yjo,11417 pigshare-0.4.0.dist-info/RECORD,, pigshare-0.4.0.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 pigshare-0.4.0.dist-info/entry_points.txt,sha256=9zalmllY8mnajD4P53q8WPabNz6qbMPgJyNufisjFPI,52 pigshare-0.4.0.dist-info/metadata.json,sha256=pqlwN28-B-n6jqg7VIZCUv6Lyko3nW1lUqqpZcw--TI,1273 pigshare-0.4.0.dist-info/top_level.txt,sha256=GRevhEnN-_PTYncW_As92pR6WPe4B_cVlt4A3XeKIws,9 PKHbj0pigshare/__init__.pyPKhGkpigshare/example.pyPKe3HɡDpigshare/helpers.pyPK3HnC~ZZpigshare/caching.pyPKr HNegpigshare/pigshare.pyPKHc & &+pigshare/models.pyPKA 5HzQpigshare/input_helpers.pyPK;HQ"X0jpigshare/admin_api.pyPKݼH|MaXX(npigshare/api.pyPK Hz-pigshare/stats_api.pyPKHN8(((&pigshare-0.4.0.dist-info/DESCRIPTION.rstPKHo44)pigshare-0.4.0.dist-info/entry_points.txtPKHiy&pigshare-0.4.0.dist-info/metadata.jsonPKH[ & pigshare-0.4.0.dist-info/top_level.txtPKHndnnpigshare-0.4.0.dist-info/WHEELPKHqdI,,!pigshare-0.4.0.dist-info/METADATAPKHlN^^;pigshare-0.4.0.dist-info/RECORDPK8A