Utilización de una API REST: Go vs Python

Tiempo de lectura ~4 minuto

Se encuentran API en todas partes hoy en día. Imagine: quiere recoger información sobre sus clientes potenciales gracias a sus emails. Bueno hay una API para hacer esto. ¿Necesita geocodar una dirección? También existe una API para hacer esto. Como desarrollador, integro regularmente nuevas API en mi sistema, en Python o en Go. Los dos métodos son bastante diferentes. Comparemolos trabajando sobre un caso “borderline”: enviar datos JSON en el body de una consulta POST.

Un ejemplo real

Recientemente utilicé la API NameAPI.org para separar un nombre entero en nombre y apellido, y conocer el género de la persona.

Su API espera datos JSON puestos en el body de una consulta POST. Ademas, el Content-Type de la consulta tiene que ser application/json y no multipart/form-data. Se trata de un caso especial porque en general se envían los datos POST a través del header de la consulta, y si se quiere enviarlos en el body de la consulta (en el caso de datos JSON complejos por ejemplo) el Content-Type común es multipart/form-data.

Aquí esta el JSON que se quiere enviar:

{
  "inputPerson" : {
    "type" : "NaturalInputPerson",
    "personName" : {
      "nameFields" : [ {
        "string" : "Petra",
        "fieldType" : "GIVENNAME"
      }, {
        "string" : "Meyer",
        "fieldType" : "SURNAME"
      } ]
    },
    "gender" : "UNKNOWN"
  }
}

Se puede hacerlo fácilmente con cURL:

curl -H "Content-Type: application/json" \
-X POST \
-d '{"inputPerson":{"type":"NaturalInputPerson","personName":{"nameFields":[{"string":"Petra Meyer","fieldType":"FULLNAME"}]}}}' \
http://rc50-api.nameapi.org/rest/v5.0/parser/personnameparser?apiKey=<API-KEY>

Y aquí esta la respuesta JSON de NameAPI.org:

{
"matches" : [ {
  "parsedPerson" : {
    "personType" : "NATURAL",
    "personRole" : "PRIMARY",
    "mailingPersonRoles" : [ "ADDRESSEE" ],
    "gender" : {
      "gender" : "MALE",
      "confidence" : 0.9111111111111111
    },
    "addressingGivenName" : "Petra",
    "addressingSurname" : "Meyer",
    "outputPersonName" : {
      "terms" : [ {
        "string" : "Petra",
        "termType" : "GIVENNAME"
      },{
        "string" : "Meyer",
        "termType" : "SURNAME"
      } ]
    }
  },
  "parserDisputes" : [ ],
  "likeliness" : 0.926699401733102,
  "confidence" : 0.7536487758945387
}

¡Ahora veamos como hacer esto en Go y en Python!

Realización en Go

Código

/*
Fetch the NameAPI.org REST API and turn JSON response into a Go struct.

Sent data have to be JSON data encoded into request body.
Send request headers must be set to 'application/json'.
*/

package main

import (
    "encoding/json"
    "io/ioutil"
    "log"
    "net/http"
    "strings"
)

// url of the NameAPI.org endpoint:
const (
    url = "http://rc50-api.nameapi.org/rest/v5.0/parser/personnameparser?" +
        "apiKey=<API-KEY>"
)

func main() {

    // JSON string to be sent to NameAPI.org:
    jsonString := `{
        "inputPerson": {
            "type": "NaturalInputPerson",
            "personName": {
                "nameFields": [
                    {
                        "string": "Petra",
                        "fieldType": "GIVENNAME"
                    }, {
                        "string": "Meyer",
                        "fieldType": "SURNAME"
                    }
                ]
            },
            "gender": "UNKNOWN"
        }
    }`
    // Convert JSON string to NewReader (expected by NewRequest)
    jsonBody := strings.NewReader(jsonString)

    // Need to create a client in order to modify headers
    // and set content-type to 'application/json':
    client := &http.Client{}
    req, err := http.NewRequest("POST", url, jsonBody)
    if err != nil {
        log.Println(err)
    }
    req.Header.Add("Content-Type", "application/json")
    resp, err := client.Do(req)

    // Proceed only if no error:
    switch {
    default:
        // Create a struct dedicated to receiving the fetched
        // JSON content:
        type Level5 struct {
            String   string `json:"string"`
            TermType string `json:"termType"`
        }
        type Level41 struct {
            Gender     string  `json:"gender"`
            Confidence float64 `json:"confidence"`
        }
        type Level42 struct {
            Terms []Level5 `json:"terms"`
        }
        type Level3 struct {
            Gender           Level41 `json:"gender"`
            OutputPersonName Level42 `json:"outputPersonName"`
        }
        type Level2 struct {
            ParsedPerson Level3 `json:"parsedPerson"`
        }
        type RespContent struct {
            Matches []Level2 `json:"matches"`
        }

        // Decode fetched JSON and put it into respContent:
        respContentBytes, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            log.Println(err)
        }
        var respContent RespContent
        err = json.Unmarshal(respContentBytes, &respContent)
        if err != nil {
            log.Println(err)
        }
        log.Println(respContent)
    case err != nil:
        log.Println("Network error:", err)
    case resp.StatusCode != 200:
        log.Println("Bad HTTP status code:", err)
    }

}

Explicaciones

Nos enfrentamos a 2 problemas:

  • tenemos que utilizar http.Client, NewRequest(), client.Do(req), y req.Header.Add("Content-Type", "application/json") par poner datos en el body y cambiar el Content-Type. Son muchas etapas.
  • recibir el JSON de NameAPI en Go es difícil porque tenemos que crear un struct que tenga la misma estructura que el JSON.

Realización en Python

Código

"""
Fetch the NameAPI.org REST API and turn JSON response into Python dict.

Sent data have to be JSON data encoded into request body.
Send request headers must be set to 'application/json'.
"""

import requests

# url of the NameAPI.org endpoint:
url = (
    "http://rc50-api.nameapi.org/rest/v5.0/parser/personnameparser?"
    "apiKey=<API-KEY>"
)

# Dict of data to be sent to NameAPI.org:
payload = {
    "inputPerson": {
        "type": "NaturalInputPerson",
        "personName": {
            "nameFields": [
                {
                    "string": "Petra",
                    "fieldType": "GIVENNAME"
                }, {
                    "string": "Meyer",
                    "fieldType": "SURNAME"
                }
            ]
        },
        "gender": "UNKNOWN"
    }
}

# Proceed, only if no error:
try:
    # Send request to NameAPI.org by doing the following:
    # - make a POST HTTP request
    # - encode the Python payload dict to JSON
    # - pass the JSON to request body
    # - set header's 'Content-Type' to 'application/json' instead of
    #   default 'multipart/form-data'
    resp = requests.post(url, json=payload)
    resp.raise_for_status()
    # Decode JSON response into a Python dict:
    resp_dict = resp.json()
    print(resp_dict)
except requests.exceptions.HTTPError as e:
    print("Bad HTTP status code:", e)
except requests.exceptions.RequestException as e:
    print("Network error:", e)

Explicaciones

¡La biblioteca Request lo hace casi todo en una sola linea: resp = requests.post(url, json=payload)!

Recibir el JSON retornado por NameAPI se hace en una linea también: resp_dict = resp.json().

Conclusión

Python es el ganador. La simplicidad de Python y su cantidad de bibliotecas disponibles nos ayudan mucho.

Aquí no hablamos del desempeño. Si el desempeño de la integracion que hace es importante para usted, Go puede ser una muy buena elección. Pero simplicidad y desempeño no están compatibles…

Also available in English | Existe aussi en français

CTOs, desarrolladores: ¿cómo elegir una buena API externa?

Se encuentran APIs en todas partes hoy en día, pero no hay que olvidar evaluar su calidad antes de integrar una. Aquí están algunos trucos para ayudar los CTOs y desarrolladores a evaluar la calidad de una API. Seguir leyendo