Skip to content

Commit e1ff457

Browse files
committed
Deprecate Keep.login and update documentation
1 parent e93d2e9 commit e1ff457

File tree

5 files changed

+73
-55
lines changed

5 files changed

+73
-55
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ An unofficial client for the [Google Keep](https://keep.google.com) API.
1212
```python
1313
import gkeepapi
1414

15+
# Obtain a master token for your account (see docs)
16+
master_token = '...'
17+
1518
keep = gkeepapi.Keep()
16-
success = keep.login('user@gmail.com', 'password')
19+
success = keep.authenticate('user@gmail.com', 'master_token')
1720

1821
note = keep.createNote('Todo', 'Eat breakfast')
1922
note.pinned = True
@@ -27,7 +30,9 @@ The code is pretty stable at this point, but you should always make backups. The
2730

2831
## Installation
2932

30-
`pip install gkeepapi`
33+
```
34+
pip install gkeepapi
35+
```
3136

3237
## Documentation
3338

docs/index.rst

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ Welcome to gkeepapi's documentation!
1313

1414
import gkeepapi
1515

16+
# Obtain a master token for your account
17+
master_token = '...'
18+
1619
keep = gkeepapi.Keep()
17-
keep.login('user@gmail.com', 'password')
20+
keep.authenticate('user@gmail.com', master_token)
1821

1922
note = keep.createNote('Todo', 'Eat breakfast')
2023
note.pinned = True
@@ -32,34 +35,40 @@ Client Usage
3235

3336
All interaction with Google Keep is done through a :py:class:`Keep` object, which is responsible for authenticating, syncing changes and tracking modifications.
3437

35-
Logging in
36-
----------
38+
Authenticating
39+
--------------
3740

38-
gkeepapi leverages the mobile Google Keep API. To do so, it makes use of :py:mod:`gpsoauth`, which requires passing in the username and password. This was necessary as the API we're using is restricted to Google applications (put differently, there is no way to enable it on the Developer Console)::
41+
The client uses the (private) mobile Google Keep API. A valid OAuth token is generated via :py:mod:`gpsoauth`, which requires a master token for the account. These tokens are so called because they have full access to your account. Protect them like you would a password::
3942

4043
keep = gkeepapi.Keep()
41-
keep.login('...', '...')
44+
keep.authenticate('user@gmail.com', master_token)
4245

43-
To reduce the number of logins you make to the server, you can store the master token after logging in. Protect this like a password, as it grants full access to your account::
46+
Rather than storing the token in the script, consider using your platform secrets store::
4447

4548
import keyring
46-
# <snip>
47-
token = keep.getMasterToken()
48-
keyring.set_password('google-keep-token', username, token)
4949

50-
You can load this token at a later point::
50+
# To save the token
51+
# ...
52+
# keyring.set_password('google-keep-token', 'user@gmail.com', master_token)
5153

52-
import keyring
53-
# <snip>
54-
token = keyring.get_password('google-keep-token', username)
55-
keep.resume(email, master_token)
54+
master_token = keyring.get_password("google-keep-token", "user@gmail.com")
55+
56+
There is also a deprecated :py:meth:`Keep.login` method which accepts a username and password. This is discouraged (and unlikely to work), due to increased security requirements on logins::
57+
58+
keep.login('user@gmail.com', 'password')
59+
60+
Obtaining a Master Token
61+
------------------------
62+
63+
Instructions can be found in the gpsoauth `documentation <https://github.com/simon-weber/gpsoauth#alternative-flow>`__. If you have Docker installed, the following one-liner prompts for the necessary information and outputs the token::
64+
65+
docker run --rm -it --entrypoint /bin/sh python:3 -c 'pip install gpsoauth; python3 -c '\''print(__import__("gpsoauth").exchange_token(input("Email: "), input("OAuth Token: "), input("Android ID: ")))'\'
5666

57-
Note: Enabling TwoFactor and logging in via an app password is recommended.
5867

5968
Syncing
6069
-------
6170

62-
gkeepapi automatically pulls down all notes after login. It takes care of refreshing API tokens, so there's no need to call :py:meth:`Keep.login` again. After making any local modifications to notes, make sure to call :py:meth:`Keep.sync` to update them on the server!::
71+
gkeepapi automatically pulls down all notes after authenticating. It takes care of refreshing API tokens, so there's no need to call :py:meth:`Keep.authenticate` again. After making any local modifications to notes, make sure to call :py:meth:`Keep.sync` to update them on the server!::
6372

6473
keep.sync()
6574

@@ -78,10 +87,10 @@ The initial sync can take a while, especially if you have a lot of notes. To mit
7887
state = json.load(fh)
7988
keep.restore(state)
8089

81-
You can also pass the state directly to the :py:meth:`Keep.login` and :py:meth:`Keep.resume` methods::
90+
You can also pass the state directly to the :py:meth:`Keep.authenticate` and the (deprecated) :py:meth:`Keep.login` methods::
8291

92+
keep.authenticate(username, master_token, state=state)
8393
keep.login(username, password, state=state)
84-
keep.resume(username, master_token, state=state)
8594

8695
Notes and Lists
8796
===============
@@ -425,7 +434,7 @@ These timestamps are all read-only.
425434
FAQ
426435
===
427436

428-
1. I get a "NeedsBrowser", "CaptchaRequired" or "BadAuthentication" :py:class:`exception.LoginException` when I try to log in.
437+
1. I get a "NeedsBrowser", "CaptchaRequired" or "BadAuthentication" :py:class:`exception.LoginException` when I try to log in. (Not an issue when using :py:meth:`Keep.authenticate`)
429438

430439
This usually occurs when Google thinks the login request looks suspicious. Here are some steps you can take to resolve this:
431440

@@ -435,7 +444,7 @@ This usually occurs when Google thinks the login request looks suspicious. Here
435444
4. Upgrading to a newer version of Python (3.7+) has worked for some people. See this `issue <https://gitlab.com/AuroraOSS/AuroraStore/issues/217#note_249390026>`__ for more information.
436445
5. If all else fails, try testing gkeepapi on a separate IP address and/or user to see if you can isolate the problem.
437446

438-
2. I get a "DeviceManagementRequiredOrSyncDisabled" :py:class:`exception.LoginException` when I try to log in.
447+
2. I get a "DeviceManagementRequiredOrSyncDisabled" :py:class:`exception.LoginException` when I try to log in. (Not an issue when using :py:meth:`Keep.authenticate`)
439448

440449
This is due to the enforcement of Android device policies on your G-Suite account. To resolve this, you can try disabling that setting `here <https://admin.google.com/AdminHome?hl=no#MobileSettings:section=advanced&flyout=security>`__.
441450

examples/resume.py

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,24 @@
2020
keep = gkeepapi.Keep()
2121

2222
token = keyring.get_password("google-keep-token", USERNAME)
23-
logged_in = False
24-
25-
# Use an existing master token if one exists
26-
if token:
27-
logger.info("Authenticating with token")
28-
try:
29-
keep.resume(USERNAME, token, sync=False)
30-
logged_in = True
31-
logger.info("Success")
32-
except gkeepapi.exception.LoginException:
33-
logger.info("Invalid token")
34-
35-
# Otherwise, prompt for credentials and login
36-
if not logged_in:
37-
password = getpass.getpass()
38-
try:
39-
keep.login(USERNAME, password, sync=False)
40-
logged_in = True
41-
del password
42-
token = keep.getMasterToken()
43-
keyring.set_password("google-keep-token", USERNAME, token)
44-
logger.info("Success")
45-
except gkeepapi.exception.LoginException as e:
46-
logger.info(e)
47-
48-
# Abort if authentication failed
49-
if not logged_in:
23+
store_token = False
24+
25+
if not token:
26+
token = getpass.getpass("Master token: ")
27+
store_token = True
28+
29+
# Authenticate using a master token
30+
logger.info("Authenticating")
31+
try:
32+
keep.authenticate(USERNAME, token, sync=False)
33+
logger.info("Success")
34+
except gkeepapi.exception.LoginException:
5035
logger.error("Failed to authenticate")
5136
sys.exit(1)
5237

38+
if store_token:
39+
keyring.set_password("google-keep-token", USERNAME, token)
40+
5341
# Sync state down
5442
keep.sync()
5543

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi"
66

77
[project]
88
name = "gkeepapi"
9-
version = "0.15.1"
9+
version = "0.16.0"
1010
authors = [
1111
{ name="Kai", email="z@kwi.li" },
1212
]

src/gkeepapi/__init__.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
""".. moduleauthor:: Kai <z@kwi.li>"""
22

3-
__version__ = "0.15.1"
3+
__version__ = "0.16.0"
44

55
import datetime
66
import http
@@ -215,7 +215,7 @@ def send(self, **req_kwargs: dict) -> dict:
215215
216216
Raises:
217217
APIException: If the server returns an error.
218-
LoginException: If :py:meth:`login` has not been called.
218+
LoginException: If session is not authenticated.
219219
"""
220220
# Send a request to the API servers, with retry handling. OAuth tokens
221221
# are valid for several hours (as of this comment).
@@ -253,7 +253,7 @@ def _send(self, **req_kwargs: dict) -> requests.Response:
253253
The raw response.
254254
255255
Raises:
256-
LoginException: If :py:meth:`login` has not been called.
256+
LoginException: If session is not authenticated.
257257
"""
258258
# Bail if we don't have an OAuth token.
259259
auth_token = self._auth.getAuthToken()
@@ -629,9 +629,11 @@ def update(self) -> Any: # noqa: ANN401
629629
class Keep:
630630
"""High level Google Keep client.
631631
632-
Stores a local copy of the Keep node tree. To start, first login::
632+
Manipulates a local copy of the Keep node tree. First, obtain a master token for your account.
633633
634-
keep.login('...', '...')
634+
To start, first authenticate::
635+
636+
keep.authenticate('...', '...')
635637
636638
Individual Notes can be retrieved by id::
637639
@@ -686,6 +688,8 @@ def login(
686688
) -> None:
687689
"""Authenticate to Google with the provided credentials & sync.
688690
691+
This flow is discouraged.
692+
689693
Args:
690694
email: The account to use.
691695
password: The account password.
@@ -696,6 +700,7 @@ def login(
696700
Raises:
697701
LoginException: If there was a problem logging in.
698702
"""
703+
logger.warning("'Keep.login' is deprecated. Please use 'Keep.authenticate' instead")
699704
auth = APIAuth(self.OAUTH_SCOPES)
700705
if device_id is None:
701706
device_id = f"{get_mac():x}"
@@ -710,6 +715,17 @@ def resume(
710715
state: dict | None = None,
711716
sync: bool = True,
712717
device_id: str | None = None,
718+
) -> None:
719+
logger.warning("'Keep.resume' has been renamed to 'Keep.authenticate'. Please update your code")
720+
self.authenticate(email, master_token, state, sync, device_id)
721+
722+
def authenticate(
723+
self,
724+
email: str,
725+
master_token: str,
726+
state: dict | None = None,
727+
sync: bool = True,
728+
device_id: str | None = None,
713729
) -> None:
714730
"""Authenticate to Google with the provided master token & sync.
715731

0 commit comments

Comments
 (0)