GitHub API v3

La idea de este documento es mostrar el uso de la API de GitHub (v3). Para esto usaremos Python 3.4.

Requerimientos

  • Librería base64, para enviar los cambios en archivos
  • Librería json, para el manejo de los argumentos a enviar y de las respuestas
  • Librería requests, para el manejo de las solicitudes a la API (Docs)

In [ ]:
# Primero tenemos que importar las librerias que usaremos
import base64
import json
import requests

# Si queremos imprimir los json de respuesta 
# de una forma mas agradable a la vista podemos usar
def print_pretty(jsonstring, indent=4, sort_keys=False):
    print(json.dumps(jsonstring, indent=indent, sort_keys=sort_keys))

La API tiene un límite de solicitudes por hora que se pueden hacer:

  • Usuario no autenticado => 60 solicitudes/hora
  • Usuario autenticado => ¡¡5000 solicitudes/hora!!

La autenticación debe contener nuestro usuario de GitHub y contraseña de la cuenta, o nuestro usuario de GitHub y un personal access token (el cual podemos crear en la configuración de nuestro perfil, siguiendo este link).

Para autenticarnos basta con enviar nuestras credenciales en cada solicitud que hagamos, por lo que conviene tenerlas en una variable:


In [ ]:
# credentials tiene el siguiente contenido como texto plano:
# usuario
# personal_access_token_o_clave

with open("credentials") as f:
    credentials = tuple(f.read().splitlines())

Para verificar que todo esté funcionando bien vamos a pedir la información del usuario autenticado, nosotros. La documentación indica:

GET /user


In [ ]:
url = "https://api.github.com/user"
req = requests.get(url, auth=credentials)

# Usaremos esto mas tarde
name = req.json()["name"]
email = req.json()["email"]

# Para obtener el json asociado: req.json()
# Pero queremos imprimirlo de una manera mas legible
print_pretty(req.json())

Esta es la respuesta a nuestra solicitud. En el header también viene información relevante:


In [ ]:
# Usamos dict(...) para formatear correctamente
# http://stackoverflow.com/a/24533335/3281097
print_pretty(dict(req.headers))

Podemos ver que en header tenemos acceso a cuantas requests nos quedan:


In [ ]:
print("Nos quedan {} solicitudes".format(dict(req.headers)['X-RateLimit-Remaining']))

Enviando información

Ya pedimos nuestra información mediante el método GET. Pero, ¿qué pasa cuando debemos usar PUT o POST? Para estos métodos HTTP tenemos que enviar argumentos. Para mostrar cómo hacerlo crearemos un repositorio y luego escribiremos un README.md. Finalmente, eliminaremos el repositorio que creemos.

Creando un repositorio

Para crear un nuevo repositorio la documentación indica:

POST /user/repos

Los argumentos de input que podemos usar se encuentran documentados.


In [ ]:
url = "https://api.github.com/user/repos"
params = {
    "name": "dummy_repo",
    "description": "An example repo using GitHub API v3",
    "homepage": "{}.github.io".format(credentials[0]),
    "private": False,
    "has_issues": True,
    "has_wiki": False,
    "has_downloads": False,
    "auto_init": False
}
req = requests.post(url, data=json.dumps(params), auth=credentials)

print(req.status_code)
print_pretty(req.json())

if req.status_code == 201:
    repo_url = req.json()["url"]
    contents_url = req.json()["contents_url"]

# Si queremos chequear el recibir un codigo 200
# assert(req.status_code == 200)

Ahora puedes ir a GitHub y ver tu repo. Como podremos notar si corremos el código dos veces, la solicitud retorna 200 (200 OK) si el repo se pudo crear correctamente y retorna 422 (422 Unprocessable Entity) si el repositorio ya se encuentra creado. Si queremos asegurarnos de que se cree y detectar cuando no se crea podemos acceder al valor de req.status_code, que contiene un int indicando el código recibido.

Escribiendo un archivo

Para crear un archivo en un repositorio la documentación correspondiente nos dice:

PUT /repos/:owner/:repo/contents/:path

Nuevamente, podemos encontrar los argumentos que nos interesen en la documentación.


In [ ]:
# Para enviar el contenido del archivo simplemente
# debemos codificarlo a base64
def encode_content(file_content):
    return base64.b64encode(file_content).decode("utf-8")


url = contents_url.replace("+path", "").format("README.md")
params = {
    "path": "README.md",
    "message": "Hice un commit usando la API de GitHub v3 :grin: :heart:",
    "content": encode_content(b"# GitHub API ftw"),
    "branch": "master",
    "author": {
        "name": name,
        "email": email
    }
}
req = requests.put(url, data=json.dumps(params), auth=credentials)

print(req.status_code)
print_pretty(req.json())

Si actualizas la página de tu repo, debería aparecer el README.md y el commit que acabamos de hacer. Como podemos comprobar, recibiremos un código 200 (200 OK) si el archivo se creó correctamente. Si volvemos a ejecutar el código recibiremos un error 422 (422 Unprocessable Entity), ¿por qué? Esto ocurre debido a que el archivo ya existe y si queremos cambiar algo en el tenemos que hacer un commit sobre el anterior, por lo que necesitamos el sha de dicho commit. Esto se puede encontrar en esta seccion de la documentación.

Eliminando un repositorio

Como el código de este archivo es solo de prueba y creamos un repositorio para mostrar el uso de la API, corresponde eliminarlo. Para esto, veamos qué dice la documentación:

DELETE /repos/:owner/:repo


In [ ]:
req = requests.delete(repo_url, auth=credentials)

if req.status_code == 204:
    print("204 No content (JSON recibido es vacío)")
else:
    print_pretty(req.json())

Si recargas nuevamente la página de tu repo, Octocat debería decirte que no existe nada en esa dirección. En este caso podemos recibir 204 (204 No Content) si el repo se elimina correctamente, 403 (403 Forbidden) si no tienes los derechos debidos sobre el repo o 404 (404 Not Found) si el repo no existe.

Notas

  • Para el último paso es necesario el scope "delete_repo", lo que se debe declarar al momento de generar el token