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 apost
.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.