Skip to content

Commit dfed979

Browse files
author
Hugo Osvaldo Barrera
committed
Port google storage to use asyncio
1 parent 8d69b73 commit dfed979

File tree

4 files changed

+81
-54
lines changed

4 files changed

+81
-54
lines changed

.builds/archlinux.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ packages:
1414
- python-click-threading
1515
- python-requests
1616
- python-requests-toolbelt
17+
- python-aiohttp-oauthlib
1718
# Test dependencies:
1819
- python-hypothesis
1920
- python-pytest-cov

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def run(self):
5656
install_requires=requirements,
5757
# Optional dependencies
5858
extras_require={
59-
"google": ["requests-oauthlib"],
59+
"google": ["aiohttp-oauthlib"],
6060
"etesync": ["etesync==0.5.2", "django<2.0"],
6161
},
6262
# Build dependencies

vdirsyncer/storage/dav.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,12 +411,18 @@ async def request(self, method, path, **kwargs):
411411

412412
# XXX: This is a temporary hack to pin-point bad refactoring.
413413
assert self.connector is not None
414-
async with aiohttp.ClientSession(
414+
async with self._session as session:
415+
return await http.request(method, url, session=session, **more)
416+
417+
@property
418+
def _session(self):
419+
"""Return a new session for requests."""
420+
421+
return aiohttp.ClientSession(
415422
connector=self.connector,
416423
connector_owner=False,
417424
# TODO use `raise_for_status=true`, though this needs traces first,
418-
) as session:
419-
return await http.request(method, url, session=session, **more)
425+
)
420426

421427
def get_default_headers(self):
422428
return {

vdirsyncer/storage/google.py

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import os
44
import urllib.parse as urlparse
5+
from pathlib import Path
56

67
import aiohttp
78
import click
@@ -21,7 +22,7 @@
2122
REFRESH_URL = "https://www.googleapis.com/oauth2/v4/token"
2223

2324
try:
24-
from requests_oauthlib import OAuth2Session
25+
from aiohttp_oauthlib import OAuth2Session
2526

2627
have_oauth2 = True
2728
except ImportError:
@@ -37,6 +38,9 @@ def __init__(
3738
url=None,
3839
connector: aiohttp.BaseConnector = None,
3940
):
41+
if not have_oauth2:
42+
raise exceptions.UserError("aiohttp-oauthlib not installed")
43+
4044
# Required for discovering collections
4145
if url is not None:
4246
self.url = url
@@ -45,68 +49,84 @@ def __init__(
4549
self._settings = {}
4650
self.connector = connector
4751

48-
if not have_oauth2:
49-
raise exceptions.UserError("requests-oauthlib not installed")
52+
self._token_file = Path(expand_path(token_file))
53+
self._client_id = client_id
54+
self._client_secret = client_secret
55+
self._token = None
5056

51-
token_file = expand_path(token_file)
52-
return self._init_token(token_file, client_id, client_secret)
57+
async def request(self, method, path, **kwargs):
58+
if not self._token:
59+
await self._init_token()
5360

54-
def _init_token(self, token_file, client_id, client_secret):
55-
token = None
56-
try:
57-
with open(token_file) as f:
58-
token = json.load(f)
59-
except OSError:
60-
pass
61-
except ValueError as e:
62-
raise exceptions.UserError(
63-
"Failed to load token file {}, try deleting it. "
64-
"Original error: {}".format(token_file, e)
65-
)
61+
return await super().request(method, path, **kwargs)
6662

67-
def _save_token(token):
68-
checkdir(expand_path(os.path.dirname(token_file)), create=True)
69-
with atomic_write(token_file, mode="w", overwrite=True) as f:
70-
json.dump(token, f)
63+
def _save_token(self, token):
64+
"""Helper function called by OAuth2Session when a token is updated."""
65+
checkdir(expand_path(os.path.dirname(self._token_file)), create=True)
66+
with atomic_write(self._token_file, mode="w", overwrite=True) as f:
67+
json.dump(token, f)
7168

72-
self._session = OAuth2Session(
73-
client_id=client_id,
74-
token=token,
69+
@property
70+
def _session(self):
71+
"""Return a new OAuth session for requests."""
72+
73+
return OAuth2Session(
74+
client_id=self._client_id,
75+
token=self._token,
7576
redirect_uri="urn:ietf:wg:oauth:2.0:oob",
7677
scope=self.scope,
7778
auto_refresh_url=REFRESH_URL,
7879
auto_refresh_kwargs={
79-
"client_id": client_id,
80-
"client_secret": client_secret,
80+
"client_id": self._client_id,
81+
"client_secret": self._client_secret,
8182
},
82-
token_updater=_save_token,
83+
token_updater=lambda token: self._save_token(token),
84+
connector=self.connector,
85+
connector_owner=False,
8386
)
8487

85-
if not token:
86-
authorization_url, state = self._session.authorization_url(
87-
TOKEN_URL,
88-
# access_type and approval_prompt are Google specific
89-
# extra parameters.
90-
access_type="offline",
91-
approval_prompt="force",
92-
)
93-
click.echo(f"Opening {authorization_url} ...")
94-
try:
95-
open_graphical_browser(authorization_url)
96-
except Exception as e:
97-
logger.warning(str(e))
98-
99-
click.echo("Follow the instructions on the page.")
100-
code = click.prompt("Paste obtained code")
101-
token = self._session.fetch_token(
102-
REFRESH_URL,
103-
code=code,
104-
# Google specific extra parameter used for client
105-
# authentication
106-
client_secret=client_secret,
88+
async def _init_token(self):
89+
try:
90+
with self._token_file.open() as f:
91+
self._token = json.load(f)
92+
except FileNotFoundError:
93+
pass
94+
except ValueError as e:
95+
raise exceptions.UserError(
96+
"Failed to load token file {}, try deleting it. "
97+
"Original error: {}".format(self._token_file, e)
10798
)
99+
100+
if not self._token:
101+
# Some times a task stops at this `async`, and another continues the flow.
102+
# At this point, the user has already completed the flow, but is prompeted
103+
# for a second one.
104+
async with self._session as session:
105+
authorization_url, state = session.authorization_url(
106+
TOKEN_URL,
107+
# access_type and approval_prompt are Google specific
108+
# extra parameters.
109+
access_type="offline",
110+
approval_prompt="force",
111+
)
112+
click.echo(f"Opening {authorization_url} ...")
113+
try:
114+
open_graphical_browser(authorization_url)
115+
except Exception as e:
116+
logger.warning(str(e))
117+
118+
click.echo("Follow the instructions on the page.")
119+
code = click.prompt("Paste obtained code")
120+
121+
self._token = await session.fetch_token(
122+
REFRESH_URL,
123+
code=code,
124+
# Google specific extra param used for client authentication:
125+
client_secret=self._client_secret,
126+
)
127+
108128
# FIXME: Ugly
109-
_save_token(token)
129+
self._save_token(self._token)
110130

111131

112132
class GoogleCalendarStorage(dav.CalDAVStorage):

0 commit comments

Comments
 (0)