Skip to content

Commit

Permalink
Support new credentials file names
Browse files Browse the repository at this point in the history
closes #169
  • Loading branch information
kuzmoyev committed Nov 10, 2023
1 parent 04dc309 commit a91537c
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 29 deletions.
20 changes: 13 additions & 7 deletions docs/source/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ There are several ways to authenticate in ``GoogleCalendar``.
Credentials file
----------------

If you have a ``credentials.json`` file (see :ref:`getting_started`), ``GoogleCalendar`` will read all the needed data
to generate the token and refresh-token from it.
If you have a ``credentials.json`` (``client_secret_*.json``) file (see :ref:`getting_started`), ``GoogleCalendar``
will read all the needed data to generate the token and refresh-token from it.

To read ``credentials.json`` from the default path (``~/.credentials/credentials.json``) use:
To read ``credentials.json`` (``client_secret_*.json``) from the default directory (``~/.credentials``) use:

.. code-block:: python
gc = GoogleCalendar()
In this case, if ``~/.credentials/token.pickle`` file exists, it will read it and refresh only if needed. If
``token.pickle`` does not exist, it will be created during authentication flow and saved alongside with
``credentials.json`` in ``~/.credentials/token.pickle``.
``credentials.json`` (``client_secret_*.json``) in ``~/.credentials/token.pickle``.

To **avoid saving** the token use:

Expand All @@ -29,15 +29,21 @@ To **avoid saving** the token use:
After token is generated during authentication flow, it can be accessed in ``gc.credentials`` field.

To specify ``credentials.json`` file path use ``credentials_path`` parameter:
To specify ``credentials.json`` (``client_secret_*.json``) file path use ``credentials_path`` parameter:

.. code-block:: python
gc = GoogleCalendar(credentials_path='path/to/credentials.json')
or

.. code-block:: python
gc = GoogleCalendar(credentials_path='path/to/client_secret_273833015691-qwerty.apps.googleusercontent.com.json')
Similarly, if ``token.pickle`` file exists in the same folder (``path/to/``), it will be used and refreshed only if
needed. If it doesn't exist, it will be generated and stored alongside the ``credentials.json`` (in
``path/to/token.pickle``).
needed. If it doesn't exist, it will be generated and stored alongside the ``credentials.json`` (``client_secret_*.json``)
(in ``path/to/token.pickle``).

To specify different path for the pickled token file use ``token_path`` parameter:

Expand Down
8 changes: 4 additions & 4 deletions docs/source/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Now you need to get your API credentials:
.. note:: You will need to enable the "Google Calendar API" for your project.

2. `Configure the OAuth consent screen`_
3. `Create a OAuth client ID credential`_ and download the ``credentials.json`` file
4. Put downloaded ``credentials.json`` file into ``~/.credentials/`` directory
3. `Create a OAuth client ID credential`_ and download the ``credentials.json`` (``client_secret_*.json``) file
4. Put downloaded ``credentials.json`` (``client_secret_*.json``) file into ``~/.credentials/`` directory


.. _`Create a new Google Cloud Platform (GCP) project`: https://developers.google.com/workspace/guides/create-project
Expand All @@ -44,15 +44,15 @@ Now you need to get your API credentials:

See more options in :ref:`authentication`.

.. note:: You can put ``credentials.json`` file anywhere you want and specify
.. note:: You can put ``credentials.json`` (``client_secret_*.json``) file anywhere you want and specify
the path to it in your code afterwords. But remember not to share it (e.g. add it
to ``.gitignore``) as it is your private credentials.

.. note::
| On the first run, your application will prompt you to the default browser
to get permissions from you to use your calendar. This will create
``token.pickle`` file in the same folder (unless specified otherwise) as your
``credentials.json``. So don't forget to also add it to ``.gitignore`` if
``credentials.json`` (``client_secret_*.json``). So don't forget to also add it to ``.gitignore`` if
it is in a GIT repository.
| If you don't want to save it in ``.pickle`` file, you can use ``save_token=False``
when initializing the ``GoogleCalendar``.
Expand Down
32 changes: 25 additions & 7 deletions gcsa/_services/authentication.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pickle
import os.path
import glob
from typing import List

from googleapiclient import discovery
Expand Down Expand Up @@ -32,10 +33,11 @@ def __init__(
Credentials with token and refresh token.
If specified, ``credentials_path``, ``token_path``, and ``save_token`` are ignored.
If not specified, credentials are retrieved from "token.pickle" file (specified in ``token_path`` or
default path) or with authentication flow using secret from "credentials.json" (specified in
``credentials_path`` or default path)
default path) or with authentication flow using secret from "credentials.json" ("client_secret_*.json")
(specified in ``credentials_path`` or default path)
:param credentials_path:
Path to "credentials.json" file. Default: ~/.credentials
Path to "credentials.json" ("client_secret_*.json") file.
Default: ~/.credentials/credentials.json or ~/.credentials/client_secret*.json
:param token_path:
Existing path to load the token from, or path to save the token after initial authentication flow.
Default: "token.pickle" in the same directory as the credentials_path
Expand Down Expand Up @@ -109,12 +111,28 @@ def _get_credentials(

@staticmethod
def _get_default_credentials_path() -> str:
"""Checks if ".credentials" folder in home directory exists. If not, creates it.
:return: expanded path to .credentials folder
"""Checks if `.credentials` folder in home directory exists and contains `credentials.json` or
`client_secret*.json` file.
:raises ValueError: if `.credentials` folder does not exist, none of `credentials.json` or `client_secret*.json`
files do not exist, or there are multiple `client_secret*.json` files.
:return: expanded path to `credentials.json` or `client_secret*.json` file
"""
home_dir = os.path.expanduser('~')
credential_dir = os.path.join(home_dir, '.credentials')
if not os.path.exists(credential_dir):
os.makedirs(credential_dir)
raise FileNotFoundError(f'Default credentials directory "{credential_dir}" does not exist.')
credential_path = os.path.join(credential_dir, 'credentials.json')
return credential_path
if os.path.exists(credential_path):
return credential_path
else:
credentials_files = glob.glob(credential_dir + '/client_secret*.json')
if len(credentials_files) > 1:
raise ValueError(f"Multiple credential files found in {credential_dir}.\n"
f"Try specifying the credentials file, e.x.:\n"
f"GoogleCalendar(credentials_path='{credentials_files[0]}')")
elif not credentials_files:
raise FileNotFoundError(f'Credentials file (credentials.json or client_secret*.json)'
f'not found in the default path: "{credential_dir}".')
else:
return credentials_files[0]
2 changes: 1 addition & 1 deletion gcsa/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ def __repr__(self):

def __lt__(self, other):
def ensure_datetime(d, timezone):
if type(d) == date:
if type(d) is date:
return ensure_localisation(datetime(year=d.year, month=d.month, day=d.day), timezone)
else:
return d
Expand Down
7 changes: 4 additions & 3 deletions gcsa/google_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ def __init__(
Credentials with token and refresh token.
If specified, ``credentials_path``, ``token_path``, and ``save_token`` are ignored.
If not specified, credentials are retrieved from "token.pickle" file (specified in ``token_path`` or
default path) or with authentication flow using secret from "credentials.json" (specified in
``credentials_path`` or default path)
default path) or with authentication flow using secret from "credentials.json" ("client_secret_*.json")
(specified in ``credentials_path`` or default path)
:param credentials_path:
Path to "credentials.json" file. Default: ~/.credentials
Path to "credentials.json" ("client_secret_*.json") file.
Default: ~/.credentials/credentials.json or ~/.credentials/client_secret*.json
:param token_path:
Existing path to load the token from, or path to save the token after initial authentication flow.
Default: "token.pickle" in the same directory as the credentials_path
Expand Down
31 changes: 24 additions & 7 deletions tests/google_calendar_tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class TestGoogleCalendarCredentials(TestCase):
def setUp(self):
self.setUpPyfakefs()

self.credentials_dir = '/.credentials'
self.credentials_dir = path.join(path.expanduser('~'), '.credentials')
self.credentials_path = path.join(self.credentials_dir, 'credentials.json')
self.fs.create_dir(self.credentials_dir)
self.fs.create_file(self.credentials_path)
Expand Down Expand Up @@ -53,20 +53,37 @@ def test_with_given_credentials_expired(self):
self.assertTrue(gc.credentials.valid)
self.assertFalse(gc.credentials.expired)

def test_get_default_credentials_path_exist(self):
self.fs.create_dir(path.join(path.expanduser('~'), '.credentials'))
def test_get_default_credentials_exist(self):
self.assertEqual(
path.join(path.expanduser('~'), '.credentials/credentials.json'),
self.credentials_path,
GoogleCalendar._get_default_credentials_path()
)

def test_get_default_credentials_path_not_exist(self):
self.assertFalse(path.exists(path.join(path.expanduser('~'), '.credentials')))
self.fs.reset()
with self.assertRaises(FileNotFoundError):
GoogleCalendar._get_default_credentials_path()

def test_get_default_credentials_not_exist(self):
self.fs.remove(self.credentials_path)
with self.assertRaises(FileNotFoundError):
GoogleCalendar._get_default_credentials_path()

def test_get_default_credentials_client_secrets(self):
self.fs.remove(self.credentials_path)
client_secret_path = path.join(self.credentials_dir, 'client_secret_1234.json')
self.fs.create_file(client_secret_path)
self.assertEqual(
path.join(path.expanduser('~'), '.credentials/credentials.json'),
client_secret_path,
GoogleCalendar._get_default_credentials_path()
)
self.assertTrue(path.exists(path.join(path.expanduser('~'), '.credentials')))

def test_get_default_credentials_multiple_client_secrets(self):
self.fs.remove(self.credentials_path)
self.fs.create_file(path.join(self.credentials_dir, 'client_secret_1234.json'))
self.fs.create_file(path.join(self.credentials_dir, 'client_secret_12345.json'))
with self.assertRaises(ValueError):
GoogleCalendar._get_default_credentials_path()

def test_get_token_valid(self):
gc = GoogleCalendar(token_path=self.valid_token_path)
Expand Down

0 comments on commit a91537c

Please sign in to comment.