paulgorman.org/technical

Python 3 REST API client with OAuth

(2023)

This is an example/exercise. We’ll access our Mastodon account from a Python script using the REST API with OAuth.

Note: many people recommend the requests library, which is slightly more ergonoic than the built-in urllib we use here. (Also note that urllib3 is unrelated to urllib. 🙄)

Log into the “Development” section of our Mastodon account (https://mastodon.social/settings/applications) and create a “New Application”. For this exercise we need permission to read:accounts and write:statuses. This gives us:

The access token defines the access privileges of the client for particular resources. The access token shown in the Mastondon interface is provided by Mastodon for testing purposes. For routine production use, a client should acquire a new access token using the client key and secret rather that using the one shown here.

$ touch api_test.py
$ chmod 700 api_test.py
#! /usr/bin/env python3

"""
This example code shows how to call a REST API with OAuth from python.

© 2023 Paul Gorman.
Distributed under the MIT License (https://opensource.org/license/mit/).
"""

import argparse
import json
import sys
import urllib.parse
import urllib.request

API_TOKEN = ".....TOKEN....."
MASTODON_ACCOUNT_ID = "1234" # Look up your Mastodon account ID with https://INSTANCE/api/v1/accounts/lookup?acct=USERNAME
MASTODON_SERVER = "https://mastodon.social/"

debug = False


def getAccount(id=MASTODON_ACCOUNT_ID, server=MASTODON_SERVER, token=API_TOKEN):
   """
   Get user account info for `id` from Mastodon server `server` using OAuth API token `token`.
   Return a Python object based on the JSON response.
   """
   MASTODON_ACCOUNT_ENDPOINT = "/api/v1/accounts/"
   url = urllib.parse.urljoin(server, MASTODON_ACCOUNT_ENDPOINT)
   url = urllib.parse.urljoin(url, id)

   headers = {
       "Authorization": "Bearer " + token
   }

   req = urllib.request.Request(url, headers=headers)   

   with urllib.request.urlopen(req) as response:
      res = json.loads(response.read())

   if debug:
      print("HTTP response:", response.status)

   return res


def postStatus(status="TEST!", visibility="public", server=MASTODON_SERVER, token=API_TOKEN):
   """
   Post new status update `status` to Mastodon server `server` using OAuth API token `token`.
   Return a Python object based on the JSON response.
   """
   MASTODON_STATUSES_ENDPOINT = "/api/v1/statuses"
   url = urllib.parse.urljoin(server, MASTODON_STATUSES_ENDPOINT)

   headers = {
       "Authorization": "Bearer " + token
   }

   query = {
     "status": status,
     "visibility": visibility
   }
   data = urllib.parse.urlencode(query)
   data = data.encode('utf-8')

   req = urllib.request.Request(url, data, headers)   

   with urllib.request.urlopen(req) as response:
      res = json.loads(response.read())

   if debug:
      print("HTTP response:", response.status)

   return res


def main():
   parser = argparse.ArgumentParser()
   parser.add_argument("--debug", help="print debug message to the command line")
   args = parser.parse_args()
   if args.debug:
      debug = True

   print(getAccount(MASTODON_ACCOUNT_ID)) 
   print(postStatus("This is a test.", "private")) 


if __name__ == "__main__":
    sys.exit(main())

References