diff --git a/README.rst b/README.rst index e621369..e24ed72 100644 --- a/README.rst +++ b/README.rst @@ -3,11 +3,6 @@ :alt: Python version supported -.. image:: http://img.shields.io/badge/python-3.5-orange.svg - :target: https://pypi.python.org/pypi/django-th/ - :alt: Python version supported - - .. image:: http://img.shields.io/badge/license-BSD-blue.svg :target: https://pypi.python.org/pypi/django-th/ :alt: License @@ -22,7 +17,7 @@ Python API for Wallabag v2.2.3 Requirements : ============== -* requests 2.13.0 +* aiohttp Installation: @@ -51,27 +46,66 @@ Creating a post : .. code:: python + #!/usr/bin/env python + + import aiohttp + import asyncio + from wallabag_api.wallabag import Wallabag # settings - params = {'username': 'foxmask', - 'password': 'mypass', - 'client_id': 'myid', - 'client_secret': 'mysecret'} my_host = 'http://localhost:8080' - # get token - token = Wallabag.get_token(host=my_host, **params) - # create a post - wall = Wallabag(host=my_host, - client_secret='mysecret', - client_id='myid', - token=token) - my_url = 'https://blog.trigger-happy.eu' - my_title = 'Trigger Happy blog' - my_tags = ['python', 'wallabag'] + async def main(loop): - wall.post_entries(url=my_url, title=my_title, tags=my_tags) + params = {'username': 'foxmask', + 'password': 'mypass', + 'client_id': 'myid', + 'client_secret': 'mysecret', + 'extension': 'pdf'} + + # get a new token + token = await Wallabag.get_token(host=my_host, **params) + + # initializing + async with aiohttp.ClientSession(loop=loop) as session: + wall = Wallabag(host=my_host, + client_secret=params.get('client_secret'), + client_id=params.get('client_id'), + token=token, + extension=params['extension'], + aio_sess=session) + + url = 'https://foxmask.trigger-happy.eu' + title = 'foxmask\'s blog' + + await wall.post_entries(url, title, '', 0, 0) + + url = 'https://trigger-happy.eu' + title = 'Project TrigerHappy' + + await wall.post_entries(url, title, '', 0, 0) + + # get all the articles + my_wallabag = await wall.get_entries() + + all_article = my_wallabag['_embedded']['items'] + + for article in all_article: + print(article['id'], article['title']) + + # get the version of wallabag + version = await wall.version + print(f"version {version}") + + # export one article into PDF + my_wallabag = await wall.get_entry_export(entry=1) + with open("foobar.pdf", "wb") as f: + f.write(my_wallabag) + + if __name__ == '__main__': + loop = asyncio.get_event_loop() + loop.run_until_complete(main(loop)) this will give you something like this : @@ -79,42 +113,6 @@ this will give you something like this : .. image:: https://github.com/foxmask/wallabag_api/blob/master/wallabag.png -3) get all the entries - -.. code:: python - - # get all the entries - wall = wall.get_entries() - - all_article = wall['_embedded']['items'] - - for article in all_article: - print(article['title']) - print(article['content']) - print("******") - -4) version of wallabag - -.. code:: python - - # get the version of your wallabag instance - print("version {}".format(wall.version)) - -5) to get the article in PDF - -.. code:: python - - # to get the article in PDF for example, - wall = Wallabag(host=my_host, - client_secret='mysecret', - client_id='myid', - token=token, - extension='pdf') - article = wall.get_entry_export(entry=1) - with open("my_file.pdf", "wb") as f: - f.write(article) - - Testing : ========= @@ -124,7 +122,7 @@ Then run the development version (with make run) Then create a client API like explain here http://doc.wallabag.org/en/v2/developer/api.html -this will give you somthing like this +this will give you something like this .. image:: https://github.com/foxmask/wallabag_api/blob/master/wallabag_api_key.png diff --git a/setup.py b/setup.py index e8b7d6e..c500cfb 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,15 @@ from setuptools import setup, find_packages from wallabag_api import __version__ as version +desc = 'Wallabag API to add every pages you want to your Wallabag account' install_requires = [ - 'requests-2.13.0', + 'aiohttp==2.2.5', ] setup( name='wallabag_api', version=version, - description='Wallabag API to add every pages you want to your Wallabag account', + description=desc, author='FoxMaSk', author_email='foxmask@trigger-happy.eu', url='https://github.com/foxmask/wallabag_api', @@ -21,7 +22,6 @@ setup( 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Topic :: Internet', 'Topic :: Communications', diff --git a/wallabag_api/__init__.py b/wallabag_api/__init__.py index c2e84e9..7941226 100644 --- a/wallabag_api/__init__.py +++ b/wallabag_api/__init__.py @@ -1,2 +1,2 @@ -VERSION = (1, 3, 0) # PEP 386 +VERSION = (1, 4, 0) # PEP 386 __version__ = ".".join([str(x) for x in VERSION]) diff --git a/wallabag_api/wallabag.py b/wallabag_api/wallabag.py index 87b8fed..aa7c746 100644 --- a/wallabag_api/wallabag.py +++ b/wallabag_api/wallabag.py @@ -3,12 +3,9 @@ import logging import aiohttp from aiohttp.http_exceptions import HttpProcessingError from aiohttp.client_exceptions import ClientResponseError -# from aiohttp import HTTPError - __author__ = 'foxmask' - logging.basicConfig(format='%(message)s', level=logging.INFO) __all__ = ['Wallabag'] @@ -40,14 +37,14 @@ class Wallabag(object): "+https://github.com/foxmask/wallabag-api", aio_sess=None): """ - init variable - :param host: string url to the official API Wallabag - :param token: string of the key provided by Wallabag - :param client_id client id - :param client_secret client secret - :param extension: xml|json|txt|csv|pdf|epub|mobi|html - :param user_agent - :param aio_session aiohttp session + init variable + :param host: string url to the official API Wallabag + :param token: string of the key provided by Wallabag + :param client_id client id + :param client_secret client secret + :param extension: xml|json|txt|csv|pdf|epub|mobi|html + :param user_agent + :param aio_sess aiohttp session """ self.host = host self.client_id = client_id @@ -57,24 +54,18 @@ class Wallabag(object): self.user_agent = user_agent self.aio_sess = aio_sess if self.format not in self.EXTENTIONS: - raise ValueError("format invalid {0} should be one of {1}".format(self.format, self.EXTENTIONS)) - - def get_host(self): - """ - get the host from which to get API - :return host - """ - return self.host + raise ValueError("format invalid {0} should be one of {1}".format( + self.format, self.EXTENTIONS)) async def query(self, path, method='get', **params): """ - Do a query to the System API + Do a query to the System API - :param path: url to the API - :param method: the kind of query to do - :param params: a dict with all the - necessary things to query the API - :return json data + :param path: url to the API + :param method: the kind of query to do + :param params: a dict with all the + necessary things to query the API + :return json data """ if method in ('get', 'post', 'patch', 'delete', 'put'): full_path = self.host + path @@ -94,17 +85,17 @@ class Wallabag(object): if resp.content_type.startswith('application/pdf') or \ resp.content_type.startswith('application/epub'): return await resp.read() - else: - return await self.handle_json_response(resp) + + return await self.handle_json_response(resp) else: raise ValueError('method expected: get, post, patch, delete, put') @staticmethod async def handle_json_response(responses): """ - get the json data response - :param responses: the json response - :return the json data without 'root' node + get the json data response + :param responses: the json response + :return the json data without 'root' node """ if responses.status != 200: raise HttpProcessingError(code=responses.status, @@ -115,7 +106,7 @@ class Wallabag(object): except ClientResponseError as e: # sometimes json_data does not return any json() without # any error. This is due to the grabbing URL which "rejects" - # the URL + # the URL logging.error("Wallabag: aiohttp error {code} {message}" .format(code=e.code, message=e.message)) return await json_data @@ -123,12 +114,12 @@ class Wallabag(object): @staticmethod def __get_attr(what, type_attr, value_attr, **kwargs): """ - - :param what: - :param type_attr: + get the value of a parm + :param what: string parm + :param type_attr: type of parm :param value_attr: :param kwargs: - :return: + :return: value of the parm """ value = int(kwargs[what]) if type_attr == 'int' else kwargs[what] if what in kwargs and value in value_attr: @@ -137,23 +128,22 @@ class Wallabag(object): # ENTRIES async def get_entries(self, **kwargs): """ + GET /api/entries.{_format} - GET /api/entries.{_format} + Retrieve all entries. It could be filtered by many options. - Retrieve all entries. It could be filtered by many options. - - :param kwargs: can contain one of the following filters - archive: '0' or '1', default '0' filter by archived status. - star: '0' or '1', default '0' filter by starred status. - delete: '0' or '1', default '0' filter by deleted status. - sort: 'created' or 'updated', default 'created' - order: 'asc' or 'desc', default 'desc' - page: int default 1 what page you want - perPage: int default 30 result per page - tags: list of tags url encoded. - since: int default 0 from what timestamp you want - Will returns entries that matches ALL tags - :return data related to the ext + :param kwargs: can contain one of the following filters + archive: '0' or '1', default '0' filter by archived status. + star: '0' or '1', default '0' filter by starred status. + delete: '0' or '1', default '0' filter by deleted status. + sort: 'created' or 'updated', default 'created' + order: 'asc' or 'desc', default 'desc' + page: int default 1 what page you want + perPage: int default 30 result per page + tags: list of tags url encoded. + since: int default 0 from what timestamp you want + Will returns entries that matches ALL tags + :return data related to the ext """ # default values params = dict({'access_token': self.token, @@ -199,17 +189,16 @@ class Wallabag(object): async def post_entries(self, url, title='', tags='', starred=0, archive=0): """ + POST /api/entries.{_format} - POST /api/entries.{_format} + Create an entry - Create an entry - - :param url: the url of the note to store - :param title: Optional, we'll get the title from the page. - :param tags: tag1,tag2,tag3 a comma-separated list of tags. - :param starred entry already starred - :param archive entry already archived - :return result + :param url: the url of the note to store + :param title: Optional, we'll get the title from the page. + :param tags: tag1,tag2,tag3 a comma-separated list of tags. + :param starred entry already starred + :param archive entry already archived + :return result """ params = {'access_token': self.token, 'url': url, 'title': title, 'tags': tags, 'starred': starred, 'archive': archive} @@ -220,13 +209,12 @@ class Wallabag(object): async def get_entry(self, entry): """ + GET /api/entries/{entry}.{_format} - GET /api/entries/{entry}.{_format} + Retrieve a single entry - Retrieve a single entry - - :param entry: \w+ an integer The Entry ID - :return data related to the ext + :param entry: \w+ an integer The Entry ID + :return data related to the ext """ params = {'access_token': self.token} url = '/api/entries/{entry}.{ext}'.format(entry=entry, @@ -235,20 +223,19 @@ class Wallabag(object): async def patch_entries(self, entry, **kwargs): """ + PATCH /api/entries/{entry}.{_format} - PATCH /api/entries/{entry}.{_format} + Change several properties of an entry - Change several properties of an entry - - :param entry: the entry to 'patch' / update - :param kwargs: can contain one of the following - title: string - tags: a list of tags tag1,tag2,tag3 - archive: '0' or '1', default '0' archived the entry. - star: '0' or '1', default '0' starred the entry - delete: '0' or '1', default '0' flag as deleted. - In case that you don't want to *really* remove it.. - :return data related to the ext + :param entry: the entry to 'patch' / update + :param kwargs: can contain one of the following + title: string + tags: a list of tags tag1,tag2,tag3 + archive: '0' or '1', default '0' archived the entry. + star: '0' or '1', default '0' starred the entry + delete: '0' or '1', default '0' flag as deleted. + In case that you don't want to *really* remove it.. + :return data related to the ext """ # default values params = {'access_token': self.token, @@ -286,13 +273,12 @@ class Wallabag(object): async def get_entry_export(self, entry): """ + GET /api/entries/{entry}/export.{_format} - GET /api/entries/{entry}/export.{_format} + Retrieve a single entry as a predefined format. - Retrieve a single entry as a predefined format. - - :param entry: \w+ an integer The Entry ID - :return data related to the ext + :param entry: \w+ an integer The Entry ID + :return data related to the ext """ params = {'access_token': self.token} url = '/api/entries/{entry}/export.{ext}'.format(entry=entry, @@ -301,14 +287,14 @@ class Wallabag(object): async def patch_entry_reload(self, entry): """ + PATCH /api/entries/{entry}/reload.{_format} - PATCH /api/entries/{entry}/reload.{_format} + Reload an entry. An empty response with HTTP Status 304 will be send + if we weren't able to update the content (because it hasn't changed + or we got an error). - Reload an entry. An empty response with HTTP Status 304 will be send if we weren't able to update - the content (because it hasn't changed or we got an error). - - :param entry: \w+ an integer The Entry ID - :return data related to the ext + :param entry: \w+ an integer The Entry ID + :return data related to the ext """ params = {'access_token': self.token} url = '/api/entries/{entry}/reload.{ext}'.format(entry=entry, @@ -317,13 +303,12 @@ class Wallabag(object): async def delete_entries(self, entry): """ + DELETE /api/entries/{entry}.{_format} - DELETE /api/entries/{entry}.{_format} + Delete permanently an entry - Delete permanently an entry - - :param entry: \w+ an integer The Entry ID - :return result + :param entry: \w+ an integer The Entry ID + :return result """ params = {'Authorization': 'Bearer {}'.format(self.token)} @@ -333,16 +318,16 @@ class Wallabag(object): async def entries_exists(self, url, urls=''): """ + GET /api/entries/exists.{_format} - GET /api/entries/exists.{_format} + Check if an entry exist by url. - Check if an entry exist by url. + :param url string true An url Url to check if it exists + :param urls string false An array of urls + (?urls[]=http...&urls[]=http...) Urls (as an array) + to check if it exists - :param url string true An url Url to check if it exists - :param urls string false An array of urls (?urls[]=http...&urls[]=http...) - Urls (as an array) to check if it exists - - :return result + :return result """ params = {'url': url, 'urls': urls} @@ -354,13 +339,12 @@ class Wallabag(object): async def get_entry_tags(self, entry): """ + GET /api/entries/{entry}/tags.{_format} - GET /api/entries/{entry}/tags.{_format} + Retrieve all tags for an entry - Retrieve all tags for an entry - - :param entry: \w+ an integer The Entry ID - :return data related to the ext + :param entry: \w+ an integer The Entry ID + :return data related to the ext """ params = {'access_token': self.token} url = '/api/entries/{entry}/tags.{ext}'.format( @@ -369,14 +353,13 @@ class Wallabag(object): async def post_entry_tags(self, entry, tags): """ + POST /api/entries/{entry}/tags.{_format} - POST /api/entries/{entry}/tags.{_format} + Add one or more tags to an entry - Add one or more tags to an entry - - :param entry: \w+ an integer The Entry ID - :param tags: string - :return result + :param entry: \w+ an integer The Entry ID + :param tags: string + :return result """ params = {'access_token': self.token, 'tags': []} if len(tags) > 0 and ',' in tags: @@ -387,28 +370,26 @@ class Wallabag(object): async def delete_entry_tag(self, entry, tag): """ + DELETE /api/entries/{entry}/tags/{tag}.{_format} - DELETE /api/entries/{entry}/tags/{tag}.{_format} + Permanently remove one tag for an entry - Permanently remove one tag for an entry - - :param entry: \w+ an integer The Entry ID - :param tag: string The Tag - :return data related to the ext + :param entry: \w+ an integer The Entry ID + :param tag: string The Tag + :return data related to the ext """ params = {'access_token': self.token} url = '/api/entries/{entry}/tags/{tag}.{ext}'.format( entry=entry, tag=tag, ext=self.format) return await self.query(url, "delete", **params) - async def get_tags(self, session): + async def get_tags(self): """ + GET /api/tags.{_format} - GET /api/tags.{_format} + Retrieve all tags - Retrieve all tags - - :return data related to the ext + :return data related to the ext """ params = {'access_token': self.token} path = '/api/tags.{ext}'.format(ext=self.format) @@ -416,13 +397,12 @@ class Wallabag(object): async def delete_tag(self, tag): """ + DELETE /api/tags/{tag}.{_format} - DELETE /api/tags/{tag}.{_format} + Permanently remove one tag from every entry - Permanently remove one tag from every entry - - :param tag: string The Tag - :return data related to the ext + :param tag: string The Tag + :return data related to the ext """ path = '/api/tags/{tag}.{ext}'.format(tag=tag, ext=self.format) params = {'access_token': self.token} @@ -430,15 +410,13 @@ class Wallabag(object): async def delete_tag_label(self, tag): """ + DELETE /api/tag/label.{_format} - DELETE /api/tag/label.{_format} + Permanently remove one tag from every entry. - Permanently remove one tag from every entry. - - :param tag: string The Tag - :return data related to the ext + :param tag: string The Tag + :return data related to the ext """ - # @TODO check if that method is well documented as its the same as delete_tags ! path = '/api/tag/label.{ext}'.format(ext=self.format) params = {'access_token': self.token, 'tag': tag} @@ -446,15 +424,13 @@ class Wallabag(object): async def delete_tags_label(self, tags): """ + DELETE /api/tags/label.{_format} - DELETE /api/tags/label.{_format} + Permanently remove some tags from every entry. - Permanently remove some tags from every entry. - - :param tags: string tags as strings (comma splitted) - :return data related to the ext + :param tags: string tags as strings (comma splitted) + :return data related to the ext """ - # @TODO check if that method is well documented as its the same as delete_tags ! path = '/api/tag/label.{ext}'.format(ext=self.format) params = {'access_token': self.token, 'tags': tags} @@ -463,15 +439,14 @@ class Wallabag(object): # ANNOTATIONS async def delete_annotations(self, annotation): """ + DELETE /api/annotations/{annotation}.{_format} - DELETE /api/annotations/{annotation}.{_format} + Removes an annotation. - Removes an annotation. + :param annotation \w+ string The annotation ID - :param annotation \w+ string The annotation ID - - Will returns annotation for this entry - :return data related to the ext + Will returns annotation for this entry + :return data related to the ext """ params = {'access_token': self.token} url = '/api/annotations/{annotation}.{ext}'.format( @@ -480,15 +455,14 @@ class Wallabag(object): async def put_annotations(self, annotation): """ + PUT /api/annotations/{annotation}.{_format} - PUT /api/annotations/{annotation}.{_format} + Updates an annotation. - Updates an annotation. + :param annotation \w+ string The annotation ID - :param annotation \w+ string The annotation ID - - Will returns annotation for this entry - :return data related to the ext + Will returns annotation for this entry + :return data related to the ext """ params = {'access_token': self.token} url = '/api/annotations/{annotation}.{ext}'.format( @@ -497,15 +471,14 @@ class Wallabag(object): async def get_annotations(self, entry): """ + GET /api/annotations/{entry}.{_format} - GET /api/annotations/{entry}.{_format} + Retrieve annotations for an entry - Retrieve annotations for an entry + :param entry \w+ integer The entry ID - :param entry \w+ integer The entry ID - - Will returns annotation for this entry - :return data related to the ext + Will returns annotation for this entry + :return data related to the ext """ params = {'access_token': self.token} url = '/api/annotations/{entry}.{ext}'.format(entry=entry, @@ -514,14 +487,13 @@ class Wallabag(object): async def post_annotations(self, entry, **kwargs): """ + POST /api/annotations/{entry}.{_format} - POST /api/annotations/{entry}.{_format} + Creates a new annotation. - Creates a new annotation. + :param entry \w+ integer The entry ID - :param entry \w+ integer The entry ID - - :return + :return """ params = dict({'access_token': self.token, 'ranges': [], @@ -542,12 +514,11 @@ class Wallabag(object): @property async def version(self): """ + GET /api/version.{_format} - GET /api/version.{_format} + Retrieve version number - Retrieve version number - - :return data related to the ext + :return data related to the ext """ params = {'access_token': self.token} url = '/api/version.{ext}'.format(ext=self.format) @@ -556,6 +527,9 @@ class Wallabag(object): @classmethod async def get_token(cls, host, **params): """ + POST /oauth/v2/token + + Get a new token :param host: host of the service :param params: will contain : @@ -574,4 +548,3 @@ class Wallabag(object): async with sess.post(host + path, data=params) as resp: data = await cls.handle_json_response(resp) return data.get("access_token") -