diff --git a/README.md b/README.md index 15e763b..f9887dd 100644 --- a/README.md +++ b/README.md @@ -4,5 +4,5 @@ Python client for Thoth's GraphQL API ``` -python3 -m pip install thothlibrary==0.2 +python3 -m pip install thothlibrary==0.4 ``` diff --git a/thothlibrary/__init__.py b/thothlibrary/__init__.py index 904c8d1..1edc165 100644 --- a/thothlibrary/__init__.py +++ b/thothlibrary/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """GraphQL client for Thoth""" -__version__ = "0.2.0" +__version__ = "0.4.0" __author__ = "Javier Arias " __copyright__ = "Copyright (c) 2020 Open Book Publishers" __license__ = "Apache 2.0" @@ -9,5 +9,6 @@ from .client import ThothClient from .errors import ThothError from .mutation import ThothMutation +from .query import ThothQuery -__all__ = ["ThothClient", "ThothMutation", "ThothError"] +__all__ = ["ThothClient", "ThothQuery", "ThothMutation", "ThothError"] diff --git a/thothlibrary/client.py b/thothlibrary/client.py index 29f9bbf..ab5230a 100644 --- a/thothlibrary/client.py +++ b/thothlibrary/client.py @@ -10,12 +10,13 @@ from graphqlclient import GraphQLClient from .auth import ThothAuthenticator from .mutation import ThothMutation +from .query import ThothQuery class ThothClient(): """Client to Thoth's GraphQL API""" - def __init__(self, thoth_endpoint): + def __init__(self, thoth_endpoint="https://api.thoth.pub"): """Returns new ThothClient object at the specified GraphQL endpoint thoth_endpoint: Must be the full URL (eg. 'http://localhost'). @@ -35,6 +36,20 @@ def mutation(self, mutation_name, data): mutation = ThothMutation(mutation_name, data) return mutation.run(self.client) + def query(self, query_name, parameters): + """Instantiate a thoth query and execute""" + query = ThothQuery(query_name, parameters) + return query.run(self.client) + + def works(self, limit: int = 100, offset: int = 0, filter_str: str = ""): + """Construct and trigger a query to obtain all works""" + parameters = { + "limit": limit, + "offset": offset, + "filter": filter_str, + } + return self.query("works", parameters) + def create_publisher(self, publisher): """Construct and trigger a mutation to add a new publisher object""" return self.mutation("createPublisher", publisher) diff --git a/thothlibrary/query.py b/thothlibrary/query.py new file mode 100644 index 0000000..7464017 --- /dev/null +++ b/thothlibrary/query.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +""" +GraphQL client for Thoth + +(c) Open Book Publishers, February 2020 +This programme is free software; you may redistribute and/or modify +it under the terms of the Apache License v2.0. +""" + + +import json +import urllib +from .errors import ThothError + + +class ThothQuery(): + """GraphQL query in Thoth + + Queries are specified in the QUERIES list, which specifies + their fields and desired return value 'fields' must be a list of + tuples (str, bool) where the string represents the attribute and the + boolean represents whether the values should be enclosed with quotes + and sanitised. + """ + + QUERIES = { + "works": { + "parameters": [ + "limit", + "offset", + "filter", + "order", + "publishers", + "workType", + "workStatus" + ], + "fields": [ + "workType", + "workStatus", + "fullTitle", + "title", + "subtitle", + "reference", + "edition", + "imprintId", + "doi", + "publicationDate", + "place", + "width", + "height", + "pageCount", + "pageBreakdown", + "imageCount", + "tableCount", + "audioCount", + "videoCount", + "license", + "copyrightHolder", + "landingPage", + "lccn", + "oclc", + "shortAbstract", + "longAbstract", + "generalNote", + "toc", + "coverUrl", + "coverCaption" + ] + } + } + + def __init__(self, query_name, parameters): + """Returns new ThothMutation object with specified mutation data + + mutation_name: Must match one of the keys found in MUTATIONS. + + mutation_data: Dictionary of mutation fields and their values. + """ + self.query_name = query_name + self.parameters = parameters + self.param_str = self.prepare_parameters() + self.fields_str = self.prepare_fields() + self.request = self.prepare_request() + + def prepare_request(self): + """Format the query request string""" + values = { + "query_name": self.query_name, + "parameters": self.param_str, + "fields": self.fields_str + } + payload = """ + query { + %(query_name)s( + %(parameters)s + ) { + %(fields)s + } + } + """ + return payload % values + + def run(self, client): + """Perform the GraphQL query and report any errors""" + result = "" + try: + result = client.execute(self.request) + if "errors" in result: + raise AssertionError + return json.loads(result)["data"][self.query_name] + except (KeyError, TypeError, ValueError, AssertionError, + json.decoder.JSONDecodeError, urllib.error.HTTPError): + raise ThothError(self.request, result) + + def prepare_parameters(self): + """Returns a string with all query parameters.""" + parameters = [] + for key, value in self.parameters.items(): + parameters.append("{}: {}, ".format(key, json.dumps(value))) + return ", ".join(parameters) + + def prepare_fields(self): + """Returns a string with all query fields.""" + return "\n".join(self.QUERIES[self.query_name]["fields"])