diff --git a/README.rst b/README.rst index d262947..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(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..1dcc671 100644 --- a/wallabag_api/__init__.py +++ b/wallabag_api/__init__.py @@ -1,2 +1,2 @@ -VERSION = (1, 3, 0) # PEP 386 +VERSION = (1, 2, 0) # PEP 386 __version__ = ".".join([str(x) for x in VERSION]) diff --git a/wallabag_api/wallabag.py b/wallabag_api/wallabag.py index b322126..aa7c746 100644 --- a/wallabag_api/wallabag.py +++ b/wallabag_api/wallabag.py @@ -1,12 +1,11 @@ # coding: utf-8 import logging -import requests -from requests import HTTPError - +import aiohttp +from aiohttp.http_exceptions import HttpProcessingError +from aiohttp.client_exceptions import ClientResponseError __author__ = 'foxmask' - logging.basicConfig(format='%(message)s', level=logging.INFO) __all__ = ['Wallabag'] @@ -26,6 +25,7 @@ class Wallabag(object): format = '' username = '' password = '' + aio_sess = None def __init__(self, host='', @@ -34,15 +34,17 @@ class Wallabag(object): client_secret='', extension='json', user_agent="WallabagPython/1.3 " - "+https://github.com/foxmask/wallabag-api"): + "+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 + 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 @@ -50,102 +52,98 @@ class Wallabag(object): self.token = token self.format = extension 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)) + raise ValueError("format invalid {0} should be one of {1}".format( + self.format, self.EXTENTIONS)) - def get_host(self): + async def query(self, path, method='get', **params): """ - get the host from which to get API - :return host - """ - return self.host + Do a query to the System API - def query(self, path, method='get', **params): - """ - 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 if method == 'get': - r = requests.get(full_path, params=params) + resp = await self.aio_sess.get(full_path, params=params) elif method == 'post': - r = requests.post(full_path, data=params) + resp = await self.aio_sess.post(full_path, data=params) elif method == 'patch': - r = requests.patch(full_path, data=params) + resp = await self.aio_sess.patch(full_path, data=params) elif method == 'delete': - r = requests.delete(full_path, headers=params) + resp = await self.aio_sess.delete(full_path, headers=params) elif method == 'put': - r = requests.put(full_path, params=params) - # return the content if its a binary one - if r.headers['Content-Type'].startswith('application/pdf') or\ - r.headers['Content-Type'].startswith('application/epub'): - return r.content - else: - return self.handle_json_response(r) + resp = await self.aio_sess.put(full_path, data=params) + + async with resp: + # return the content if its a binary one + if resp.content_type.startswith('application/pdf') or \ + resp.content_type.startswith('application/epub'): + return await resp.read() + + return await self.handle_json_response(resp) else: raise ValueError('method expected: get, post, patch, delete, put') @staticmethod - def handle_json_response(responses): + 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_code != 200: - raise HTTPError(responses.status_code, responses.json()) + if responses.status != 200: + raise HttpProcessingError(code=responses.status, + message=await responses.json()) json_data = {} try: json_data = responses.json() - except: - # sometimes json_data does not return any json() without + 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 - if 'errors' in json_data: - for error in json_data['errors']: - error_json = json_data['errors'][error]['content'] - logging.error("Wallabag: {error}".format(error=error_json)) - return json_data + # the URL + logging.error("Wallabag: aiohttp error {code} {message}" + .format(code=e.code, message=e.message)) + return await json_data @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.get(what) + value = int(kwargs[what]) if type_attr == 'int' else kwargs[what] if what in kwargs and value in value_attr: return value # ENTRIES - def get_entries(self, **kwargs): + 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, @@ -156,91 +154,88 @@ class Wallabag(object): 'order': 'desc', 'page': 1, 'perPage': 30, - 'tags': [], + 'tags': '', 'since': 0}) params['archive'] = self.__get_attr(what='archive', type_attr=int, value_attr=(0, 1), - **kwargs) + **params) params['star'] = self.__get_attr(what='star', type_attr=int, value_attr=(0, 1), - **kwargs) + **params) params['delete'] = self.__get_attr(what='delete', type_attr=int, value_attr=(0, 1), - **kwargs) + **params) params['order'] = self.__get_attr(what='order', type_attr=str, value_attr=('asc', 'desc'), - **kwargs) + **params) if 'page' in kwargs and isinstance(kwargs['page'], int): params['page'] = kwargs['page'] if 'perPage' in kwargs and isinstance(kwargs['perPage'], int): params['perPage'] = kwargs['perPage'] if 'tags' in kwargs and isinstance(kwargs['tags'], list): - params['tags'] = kwargs['tags'] + params['tags'] = ', '.join(kwargs['tags']) if 'since' in kwargs and isinstance(kwargs['since'], int): params['since'] = kwargs['since'] path = '/api/entries.{ext}'.format(ext=self.format) - return self.query(path, "get", **params) + return await self.query(path, "get", **params) - def post_entries(self, url, title='', tags='', starred=0, archive=0): + 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} if len(tags) > 0 and ',' in tags: params['tags'] = tags.split(',') path = '/api/entries.{ext}'.format(ext=self.format) - return self.query(path, "post", **params) + return await self.query(path, "post", **params) - def get_entry(self, entry): + 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, ext=self.format) - return self.query(url, "get", **params) + return await self.query(url, "get", **params) - def patch_entries(self, entry, **kwargs): + 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, @@ -274,246 +269,231 @@ class Wallabag(object): path = '/api/entries/{entry}.{ext}'.format( entry=entry, ext=self.format) - return self.query(path, "patch", **params) + return await self.query(path, "patch", **params) - def get_entry_export(self, entry): + 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, ext=self.format) - return self.query(url, "get", **params) + return await self.query(url, "get", **params) - def patch_entry_reload(self, entry): + 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, ext=self.format) - return self.query(url, "patch", **params) + return await self.query(url, "patch", **params) - def delete_entries(self, entry): + 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)} path = '/api/entries/{entry}.{ext}'.format( entry=entry, ext=self.format) - return self.query(path, "delete", **params) + return await self.query(path, "delete", **params) - def entries_exists(self, url, urls=''): + 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} path = '/api/entries/exists.{ext}'.format(ext=self.format) - return self.query(path, "get", **params) + return await self.query(path, "get", **params) # TAGS - def get_entry_tags(self, entry): + 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( entry=entry, ext=self.format) - return self.query(url, "get", **params) + return await self.query(url, "get", **params) - def post_entry_tags(self, entry, tags): + 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: params['tags'] = tags.split(',') path = '/api/entries/{entry}/tags.{ext}'.format( entry=entry, ext=self.format) - return self.query(path, "post", **params) + return await self.query(path, "post", **params) - def delete_entry_tag(self, entry, tag): + 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 self.query(url, "delete", **params) + return await self.query(url, "delete", **params) - def get_tags(self): + 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) - return self.query(path, "get", **params) + return await self.query(path, "get", **params) - def delete_tag(self, tag): + 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} - return self.query(path, "delete", **params) + return await self.query(path, "delete", **params) - def delete_tag_label(self, tag): + 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} - return self.query(path, "delete", **params) + return await self.query(path, "delete", **params) - def delete_tags_label(self, tags): + 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} - return self.query(path, "delete", **params) + return await self.query(path, "delete", **params) # ANNOTATIONS - def delete_annotations(self, annotation): + 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( annotation=annotation, ext=self.format) - return self.query(url, "delete", **params) + return await self.query(url, "delete", **params) - def put_annotations(self, annotation): + 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( annotation=annotation, ext=self.format) - return self.query(url, "put", **params) + return await self.query(url, "put", **params) - def get_annotations(self, entry): + 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, ext=self.format) - return self.query(url, "get", **params) + return await self.query(url, "get", **params) - def post_annotations(self, entry, **kwargs): + 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': [], @@ -528,26 +508,29 @@ class Wallabag(object): url = '/api/annotations/{entry}.{ext}'.format(entry=entry, ext=self.format) - return self.query(url, "post", **params) + return await self.query(url, "post", **params) # VERSION @property - def version(self): + 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) - return self.query(url, "get", **params) + return await self.query(url, "get", **params) @classmethod - def get_token(cls, host, **params): + 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 : @@ -559,7 +542,9 @@ class Wallabag(object): :return: access token """ - params['grant_type'] = ["password"] + params['grant_type'] = "password" path = "/oauth/v2/token" - r = requests.post(host + path, data=params) - return cls.handle_json_response(r)['access_token'] + async with aiohttp.ClientSession() as sess: + async with sess.post(host + path, data=params) as resp: + data = await cls.handle_json_response(resp) + return data.get("access_token")