from logging import getLogger
from os import getenv
from typing import Dict, Optional
from pyinaturalist.api_requests import post
from pyinaturalist.constants import API_V0_BASE_URL, KEYRING_KEY
from pyinaturalist.exceptions import AuthenticationError
logger = getLogger(__name__)
[docs]def get_access_token(
username: str = None,
password: str = None,
app_id: str = None,
app_secret: str = None,
user_agent: str = None,
) -> str:
"""Get an access token using the user's iNaturalist username and password.
You still need an iNaturalist app to do this.
**API reference:** https://www.inaturalist.org/pages/api+reference#auth
See :ref:`auth` for additional options for storing credentials.
Examples:
With direct keyword arguments:
>>> from pyinaturalist.auth import get_access_token
>>> access_token = get_access_token(
>>> username='my_username',
>>> password='my_password',
>>> app_id='33f27dc63bdf27f4ca6cd95dd9dcd5df',
>>> app_secret='bbce628be722bfe2abd5fc566ba83de4',
>>> )
With environment variables or keyring configured:
>>> access_token = get_access_token()
If you would like to run custom requests for endpoints not yet implemented in pyinaturalist,
you can authenticate these requests by putting the token in your HTTP headers as follows:
>>> import requests
>>> requests.get(
>>> 'https://www.inaturalist.org/observations/1234',
>>> headers={'Authorization': f'Bearer {access_token}'},
>>> )
Args:
username: iNaturalist username
password: iNaturalist password
app_id: iNaturalist application ID
app_secret: iNaturalist application secret
user_agent: a user-agent string that will be passed to iNaturalist.
Raises:
:py:exc:`requests.HTTPError` (401) if credentials are invalid
"""
payload = {
'username': username or getenv('INAT_USERNAME'),
'password': password or getenv('INAT_PASSWORD'),
'client_id': app_id or getenv('INAT_APP_ID'),
'client_secret': app_secret or getenv('INAT_APP_SECRET'),
'grant_type': 'password',
}
# If neither args nor envars were given, then check the keyring
if not all(payload.values()):
payload.update(get_keyring_credentials())
if not all(payload.values()):
raise AuthenticationError('Not all authentication parameters were provided')
response = post(
f'{API_V0_BASE_URL}/oauth/token',
json=payload,
user_agent=user_agent,
)
response.raise_for_status()
return response.json()['access_token']
[docs]def get_keyring_credentials() -> Dict[str, Optional[str]]:
"""Attempt to get iNaturalist credentials from the system keyring
Returns:
OAuth-compatible credentials dict
"""
# Quietly fail if keyring package is not installed
try:
from keyring import get_password
from keyring.errors import KeyringError
except ImportError:
logger.warning('Optional dependency `keyring` not installed')
return {}
# Quietly fail if keyring backend is not available
try:
return {
'username': get_password(KEYRING_KEY, 'username'),
'password': get_password(KEYRING_KEY, 'password'),
'client_id': get_password(KEYRING_KEY, 'app_id'),
'client_secret': get_password(KEYRING_KEY, 'app_secret'),
}
except KeyringError as e:
logger.warning(str(e))
return {}
[docs]def set_keyring_credentials(
username: str,
password: str,
app_id: str,
app_secret: str,
):
"""
Store iNaturalist credentials in the system keyring for future use.
Args:
username: iNaturalist username
password: iNaturalist password
app_id: iNaturalist application ID
app_secret: iNaturalist application secret
"""
from keyring import set_password
set_password(KEYRING_KEY, 'username', username)
set_password(KEYRING_KEY, 'password', password)
set_password(KEYRING_KEY, 'app_id', app_id)
set_password(KEYRING_KEY, 'app_secret', app_secret)