Saltar a contenido

Desarrollo

Preparación de entorno de desarrollo

git clone https://github.com/mondeja/http-request-codegen.git
python3 -m virtualenv venv
. venv/bin/activate
python3 -m pip install .[dev]
pre-commit install
git clone https://github.com/mondeja/http-request-codegen.git
python3 -m virtualenv venv
venv\Scripts\activate.bat
python3 -m pip install .[dev]
pre-commit install

Comandos de desarrollo

Testeo

pytest -sv
pytest -s --cov=http_request_codegen --cov-config=setup.cfg --cov-report=html
pytest -svv --doctest-modules http_request_codegen

Linteado

pre-commit run --all-files

Desarrollando implementaciones

Para desarrollar una función de un método HTTP para una bilbioteca o un programa, necesitas tener en cuenta todos los parámetros descritos en la función generate_http_request_code, pero no los valores aleatorios pasados en el argumento parameters ya que http_request_codegen provee funciones que pueden manejarlos.

Argumentos de implementación

Cada función de implementación debe contener los siguientes argumentos, que son pasados de la función de la API generate_http_request_code, por lo que se recomienda que te familiarices con los argumentos de esa función antes de continuar con esta guía porque están bien documentados ahí:

  • url: único agumento posicional de la función, representa la URL objetivo de la petición.
  • headers: diccionario de encabezados.
  • parameters: lista de objetos de datos de parámetros.
  • files: diccionario de archivos, sólo pasados a peticiones POST, por lo que esta argumento no debe ser definido si el nombre de la función de implementación es diferente a post.
  • wrap: ancho máximo del trozo de código renderizado.
  • indent: indentación usada en el trozo de código generado.
  • quote_char: caracter de limitación de cadenas.
  • setup : trozo de código colocado al principio de la petición generada.
  • teardown : trozo de código colocado al final de la petición generada.
  • oneline: si se habilita, la renderización del trozo de código se hará en una línea.
  • seed: semilla usada generando los valores aleatorios falsos de los parámetros.
  • locale: idioma usado por la biblioteca faker para localizar los valores aleatorios falseados para los parámetros.

Singularidades de métodos

POST

    Most POST methods implementations render their code snippets different,
    depending on *Content-Types* header, including by default some of the
    most used *Content-Types* header related behaviours:

    - The default behavior, even if you don't specify it explicitly in the
        *Content-Type* header is the generation of an
        `application/x-www-form-urlencoded` encoded request.
    - If you want to generate a ``multipart/form-data`` encoded request,
        you need to specify the files to sent using the ``files`` argument.
    - If you specifies the *Content-Type* header `application/json`, the
        parameters sent will be adjusted according to the JSON encoded POST
        request.
    - If you specifies the *Content-Type* header `text/plain`, you can only
        send one parameter and it will be adjusted accordingly following
        the implementation.

Comportamiento de envoltura en una línea

La primera cosa a tener en cuenta (y la más complicada) es el comportamiento de envoltura (argumento wrap) renderizando como si oneline=True fuera pasado. La pregunta es: ¿puede un trozo de código ser renderizado en una línea si el largo estimado de la petición es menor que el valor del argumento wrap?

Por ejemplo, peticiones con la biblioteca requests de Python pueden ser renderizadas usando este tipo de código, en una línea:

import requests

req = requests.get('https://github.com/mondeja/http-request-codegen')

...o usando múltiples líneas (wrap es menor que el largo esperado):

import requests

req = requests.get(
  'https://github.com/mondeja/http-request-codegen'
)

Por supuesto, esto también afecta a los argumentos parameters, headers y kwargs:

import requests

req = requests.get('localhost', params={'foo': 'bar'}, headers={'foo': 'bar'})

...los cuales pueden ser renderizados en múltiples líneas:

import requests

req = requests.get(
    'localhost',
    params={'foo': 'bar'},
    headers={'foo': 'bar'}
)

Ya que este comportamiento puede depender tanto de los argumentos oneline y wrap, la forma recomendada de implementar esto es calcular el largo de la petición esperada dentro del trozo de código y, si es mayor o igual al argumento wrap, debe ser renderizada como si oneline=True.

Tip

Puedes ver un ejemplo de este tipo de implementación en la función http_request_codegen.generators.python.requests::get.

Pero otras implementaciones podrían ser renderizadas en múltiples líneas sin importar el valor del argumento wrap.

Por ejemplo, la implementación de la API fetch de Javascript siempre renderizará un trozo de código multilínea (a no ser que oneline=True sea explícitamente definido), ya que la escritura de promesas Javascript en una sóla línea no es una sintaxis común y hay muy poco margen para que la petición generada pudiera no tener que ser evuelta dado el valor de envolura por defecto (80 en este caso). El trozo de código mínimo razonable en una línea para la implementación fetch de la API Javascript sería:

fetch('localhost').then(function(response) {}).catch(function(error) {console.error(error)});

...lo cual excede el valor por defecto del argumento wrap (80). En este tipo de casos, no hay necesidad de calcular el largo de el trozo de código de la petición antes de construir la salida.

Tip

Puedes ver un ejemplo de este tipo de implementación en la función http_request_codegen.generators.javascript.fetch::get.

En el primer caso, necesitas iterar por los argumentos parameters, headers y kwargs para calcular el largo esperado, entonces comparar el largo esperado con el valor del argumento wrap y, si lo alcanza, definir una variable interna para el comportamiento oneline=True. En el segundo, puedes asumir que el código generado es multilínea a no ser que oneline=True sea definido explícitamente como argumento.

Aleatorizando valores

La biblioteca provee las funciones lazy_name_by_parameter y lazy_value_by_parameter las cuales retornan el nombre el valor de un parámetro dada un diccionario de especificación de parámetro. Estos deben ser usados para aleatorizar parámetros de una forma unificada entre implementaciones como se describe en la documentación de la función generate_http_request_code.

Utilidades de lenguaje/plataforma

Puedes crear un módulo _utils.py dentro de un paquete de lenguaje o plataforma para almacenar utilidades que puedan ayudar en el proceso de construcción del trozo de código, como:

  • Definir la indentación por defecto para el lenguaje/plataforma (argumento indent).
  • Definir el valor por defecto de envoltura (argumento wrap).
  • Definir los caracteres de delimitación de cadenas (argumento quote_char).
  • Escapar delimitadores de cadenas de valores (de acuerdo al argumento quote_char dado).
  • Crear funciones de más alto nivel de generación de código para el lenguaje/plataforma, como definiciones de cadenas de comportamientos de envoltura, definiciones de diccionarios...

Tip

Ver los módulos _utils.py actuales de los paquetes de generators como referencia.

Creando casos de tests

Usa el script scripts/create-impl-test-cases.py para crear casos posibles de generación de trozos de código de acuerdo a combinaciones de argumentos. Esto te ayudará desarrollando implementaciones ya que te ahorra la necesidad de ejecutar cada posible combinación de argumentos. Úsalo tal como sigue:

rm -rf cases && python3 scripts/create-impl-test-cases.py \
  --language python \
  --implementation requests \
  --method GET \
  --directory cases

El comando previo creará un directorio cases/ con un montón de trozos de código generados, dadas las combinaciones descritas en tests/combinations.py.

Cuando hayas revisado manualmente que todos los trozos de código son generados correctamente, puedes crear un test para la implementación en tests/test_generators/test_<lang>/test_<impl>/test_<impl>.py, colocando el directorio cases/ en tests/test_generators/test_<lang>/test_<impl>/<METHOD>.

Por ejemplo, para el método GET de la biblioteca requests de Python, el módulo de test estaría en tests/test_generators/test_python/test_requests/test_requests.py y el directorio cases/ estaría emplazado en tests/test_generators/test_python/test_requests/GET/.

Tip

Puedes usar un módulo de test ya implementado como referencia escribiendo uno para la implementación.