La idea de este documento es mostrar el uso de la API de GitHub (v3). Para esto usaremos Python 3.4.
base64
, para enviar los cambios en archivosjson
, para el manejo de los argumentos a enviar y de las respuestasrequests
, 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:
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']))
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.
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.
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.
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.