Categoría: Desarrollo

  • Automatizar el reemplazo de un método en tests

    Método para automatizar el reemplazo de un método en el código a comprobar.

    class BaseTestEnvironment(unittest.IsolatedAsyncioTestCase):
        def _patch_method(self):
            async def new_method(arg_1: Any, *args: Any):
                return "anything"
    
            patcher = patch.object(
                TheClass,
                "the_method",
                side_effect=new_method,
            )
            self._m_method = patcher.start()
            self.addCleanup(patcher.stop)

    Llamando desde cualquier test a self._patch_method se realizará el patch del método TheClass.the_method durante la ejecución del test.

  • Forzar como cadena el valor de una celda en Google Sheets API

    Cuando se edita manualmente el valor de una celda en Google Sheets, el formato se elige automáticamente según el contenido, si se introduce 1234 se tomará como un número, si se introduce 2024-07-08 como una fecha, asdfg como una cadena,… El problema viene cuando queremos introducir una cadena que parece otra cosa.

    Supongamos que tenemos una serie de identificadores hexadecimales, 01af38 es tratado como una cadena, pero 983193 se interpreta como un número.

    Si queremos que un valor sea una cadena en el interfaz de usuario de Google Sheets podemos preceder el valor del apóstrofe ':

    Esto funciona bien en el UI, pero al utilizar la API resulta que si pasamos '983193, lo interpreta como una cadena, por empezar por ' y le añade un ' más, quedando el resultado así:

    Esto es debido al tipo de entrada usado por defecto en la API, para que trate ese apóstrofe como si se estuviera tecleando en el UI de la celda hay que utilizar el tipo de entrada USER_ENTERED.

  • Mi VSCode settings.json

    Este es el contenido (en evolución) de mi fichero de configuración de VSCode. Está únicamente orientado a Python, ya que es lo único que utilizo ahora mismo. No está aun completo, hay tareas (como la inserción automática de los import necesarios) con las que aun no me siento lo suficientemente cómodo en VSCode.

    {
    "debug.console.fontSize": 14,
    "diffEditor.codeLens": true,"editor.fontSize": 14,
    "editor.minimap.showSlider": "always",
    "editor.rulers": [88],
    "editor.wordWrap": "wordWrapColumn",
    "editor.wordWrapColumn": 88,
    "editor.wrappingIndent": "indent",
    
    "gitlens.currentLine.enabled": false,
    "gitlens.hovers.currentLine.over": "line",
    
    "remote.SSH.defaultExtensions": [
        "gitpod.gitpod-remote-ssh"
    ],
    "remote.SSH.configFile": "/var/folders/qn/whatever/T/gitpod_ssh_config-more-whatever",
    
    "python.analysis.autoImportCompletions": true,
    "python.analysis.completeFunctionParens": true,
    "python.analysis.diagnosticSeverityOverrides": {
        "reportMissingTypeStubs": "information"
    },
    "python.analysis.indexing": true,
    "python.analysis.typeCheckingMode": "strict",
    "python.analysis.useLibraryCodeForTypes": true,
    "python.diagnostics.sourceMapsEnabled": true,
    "python.envFile": "${workspaceFolder}/.env",
    "python.formatting.autopep8Args": [
        "\"--max-line-length\", \"89\""
    ],
    "python.languageServer": "Pylance",
    "python.linting.banditEnabled": true,
    "python.linting.mypyEnabled": true,
    "python.terminal.activateEnvInCurrentTerminal": true,
    "python.testing.pytestEnabled": true,
    "python.testing.unittestEnabled": true,
    "vsintellicode.python.completionsEnabled": true,
    
    "scm.inputFontSize": 14,
    "terminal.integrated.fontSize": 14,
    "vim.textwidth": 89,
    "workbench.fontAliasing": "antialiased",
    "workbench.tree.indent": 16,
    }

  • Obtener valores por defecto para columnas de una tabla PostgreSQL

    En PostgreSQL la forma de asignar valores automaticamente a una columna (AUTOINCREMENT en MariaDB, por ejemplo) es mediante la asociacion de una secuencia de la que obtener los valores para la columna. Esto se hace automaticamente sin que nos tengamos que preocupar si el tipo elegido para la columna al crearla es SERIAL, que equivale a asignar a dicha columna la propiedad IDENTITY del estandar SQL [1].

    Pero si la tabla ya esta creada, poblada y en uso, que hay que hacer?

    Pues hay que crear la secuencia y asignarsela a la columna explicitamente [2]:

    CREATE SEQUENCE public.table_id_seq
        INCREMENT 1
        START 1000;
    
    ALTER TABLE public.table
        ALTER COLUMN id SET DEFAULT nextval('table_id_seq')
    
    ALTER SEQUENCE public.table_id_seq
        OWNER TO table.id;
    
    COMMIT;

    Y si ya he creado la sequencia y la he asociado a la columna pero se me ha olvidado decirle a la secuencia quien es su propietario/

    Pues se le dice despues, no pasa nada;

    ALTER SEQUENCE public.table_id_seq
        OWNED BY public.items.id;

    Y ya que estamos, tengo otra columna para la que tambien quiero valores por defecto, pero esta vez quiero el momento de la insercion.

    Esta es facil, basta con modificar la columna en cuestion:

    ALTER TABLE public.items
    ALTER COLUMN create_date SET DEFAULT current_timestamp;

    Referencias:

    1. https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL
    2. https://dba.stackexchange.com/a/78735/189526

     

  • Importar un módulo Python dinámicamente

    Para poder utilizar un mismo código Python que tenía duplicado con distintas configuraciones en distintos directorios pensé en poner las variables que definen la configuración del trabajo a realizar en ficheros .py particulares y cargarlos como un módulo desde el ahora único fichero de código con lógica. Pensé en esta chapuza de ficheros de configuración para ahorrarme trabajo y no tener que analizar archivos JSON, YAML o cualquier otro formato. Al final el trabajo me lo ahorré gracias a un experto en Python que trabaja conmigo.

    Para resumir digamos que tengo X ficheros Python del estilo de estos dos:

    #!/usr/bin/python
    #Configuración 1
    variable = "Por aquí"
    #Lógica
    print variable
    #!/usr/bin/python
    #Configuración 2
    variable = "Por allá"
    #Lógica
    print variable

    Lo que quería hacer es tener un fichero único con la lógica y un fichero particular por cada configuración importado desde el fichero de lógica. Así, los ficheros de configuración serían los siguientes:

    #!/usr/bin/python
    #Configuración 1
    variable = "Por aquì"
    #!/usr/bin/python
    #Configuración 2
    variable = "Por allá"

    Al fichero de lógica le proporcionaría la ruta del fichero de configuración a importar y listo. Pero en el fichero de lógica no se puede utilizar un simple import, ya que el nombre de los ficheros de configuración a importar es dinámico.

    Buscando por ahí llegué hasta el punto en que teniendo la ruta del fichero de configuración preparada, lograba (más o menos) importar el fichero de configuración utilizando el módulo importlib. Aclaro que el entorno en el que trabajo es Python 2.7, aunque creo, que esto funciona igual en Python 3. Con la documentación y los artículos que había leído llegué hasta este código que requiere como primer parámetro la ruta del directorio donde buscar el módulo y como segundo el nombre del módulo (nombre del fichero eliminando el sufijo .py):

    #!/usr/bin/python
    import os
    import sys
    # Obvio las comprobaciones de número de parámetros y existencia de rutas
    directorioConfiguracion = os.path.abspath(sys.argv[1])
    nombreModulo = sys.argv[2]

    # Aquí se le dice donde buscar el módulo
    sys.path.append(directorioConfiguracion)

    # Aquí trato (sin éxito aún) de importarlo correctamente
    import importlib
    importlib.import_module(nombreModulo)

    # Aquí se ve que el módulo se ha "cargado" con el nombre proporcionado como nombreModulo
    print sys.modules

    El módulo era encontrado y cargado, pero no veía la forma de poder acceder a las variables que había en él. Y, (muy) probablemente, seguiría sin verla mientras escribo esto de no haber consultado al mencionado experto compañero. Él, echando un vistazo me dijo: «¿no tendrías que asignar el resultado del import_module a una variable?». Ahora me parece obvio que debía ser así.

    Así, el código correcto resultó ser el siguiente:

    #!/usr/bin/python
    import os
    import sys
    # Obvio las comprobaciones de número de parámetros y existencia de rutas
    directorioConfiguracion = os.path.abspath(sys.argv[1])
    nombreModulo = sys.argv[2]

    # Aquí se le dice donde buscar el módulo
    sys.path.append(directorioConfiguracion)

    # Aquí trato (sin éxito aún) de importarlo correctamente
    import importlib
    moduloConfiguracion = importlib.import_module(nombreModulo)

    #Lógica
    print moduloConfiguracion.variable


  • Mover una transacción git a otra rama

    ¿Nunca has hecho una transacción en git y te has dado cuenta que deberías haberla puesto en otra rama? Yo lo hago todos los días.

    Afortunadamente, es posible arreglar esto y dejarlo tal como si hubieras sido tan estrictamente riguroso en el control del árbol de cambios como se supone que se debe ser. La verdad es que yo sigo sin entender muchos detalles de cómo funciona git, pero parece que cualquier cosa que puedas imaginar o necesitar puede hacerse.

    Originalmente reproduje aquí los pasos para mover una transacción ya hecha hacia una nueva rama, tal como se indica a continuación. A posteriori recurrí a este mismo recurso para mover una transacción a otra rama ya existente, cuyos pasos incluyo en el segundo ejemplo.

    Los pasos (aparentemente más correctos) para mover la última transacción a una nueva rama son los siguientes:

    git co -b nueva_rama HEAD~1
    git branch --set-upstream-to=rama_original
    git cherry-pick ..rama_original
    git branch --force rama_original rama_original~1

    Créditos para esta respuesta en StackOverflow: Move the most recent commit(s) to a new branch with Git

    Si lo que se quiere es mover la transacción a una rama ya existente se haría así:

    git co rama_destino
    git cherry-pick rama_original~1..rama_original
    git branch --force rama_original rama_original~1
    

    Si no es solo una transacción sino varias seguidas se podría hacer cambiando el ~1 utilizado por el número de transacciones a mover.

    ¡Ojo!, si en el camino se han creado nuevas ramas o si se han subido (push) los cambios a un repositorio se pueden originar graves complicaciones, do it at  your own risk.

     

     

  • Deshacer un merge de git después de hacer un push como si no hubiera pasado nada

    La receta es simple, basta con devolver la rama erróneamente mezclada (merge) a su estado anterior y forzar la actualización en el repositorio (push).

    Sin embargo, hay que tener en cuenta que hay unos requisitos previos para garantizar que esto salga bien.

    1. Nadie, debe haber actualizado su espacio de trabajo con los cambios subidos al repositorio que queremos deshacer.
    2. El merge debe haberse hecho con la opción –no-ff (no fast-forward).
    3. No deben haberse realizado más commits después del del merge que queremos deshacer.

    De estos tres requisitos el primero es el más importante, si deshacemos estos cambios habiendo actualizado otra persona su espacio de trabajo con el contenido del repositorio estaremos creando dos universos paralelos divergentes.

    Los otros dos son más bien un «allá tú» si no los cumples. Es decir, puede que te funcione sin cumplirlos, pero si ese es tu caso, mejor mira en otros sitios donde se trate un caso más parecido al tuyo.

    Así que si estás seguro de que cumples estos requisitos solo tienes que ejecutar este par de comandos:

    git reset --hard <sha-commit-previo-a-merge>
    git push <repositorio> <rama> --force

    El sha-commit-previo-a-merge será la firma del commit previo al merge en la rama que queremos restaurar. Para saber exactamente cuál es el commit previo en la rama en la que hemos realizado el merge ejecutamos este comando:

    git log --first-parent <rama>

    El commit previo al del merge que se quiere deshacer (el actual si no hemos seguido haciendo commits, tal como indica el tercer requisito) será el que debemos proporcionar para el git reset.

    Y eso es todo. Las referencias que he consultado, entre otras, para atreverme a ejectuar esos comandos en mi repositorio son las siguientes:

  • Cómo decirle a Eclipse que el código es C++11

    En un proyecto C++ con Makefile, Eclipse no reconocía la parte de la librería estándar correspondiente a C++11.

    Abriendo el fichero /usr/include/c++/4.8.2/mutex se comprueba que el problema es el valor definido para __cplusplus que Eclipse proporciona al analizador del código.

    Para hacer que Eclipse le diga a su analizador que el código es C++11 hay que hacerlo en la configuración de rutas y macros del preprocesador (Project->Properties->C/C++ General->Preprocessor Include Paths, Macros, etc.: Pestaña «Providers»). Allí hay que añadir el parámetro -std=c++11 al comando utilizado para obtener las características del compilador.

    Una vez añadido, el valor de __cplusplus será 201103L, con lo que se activarán los includes de la librería estándar correspondientes a C++11.

    Edición 5 de enero de 2018:

    En los proyectos C++ con Makefile generado por Eclipse también es necesario indicar la versión C++ si esta no es ISO C++98. Si utilizamos código C++11 sin haber configurado el proyecto Eclipse para soportar esta versión nos dará el correspondiente error en la compilación:

    Para indicarle en esta ocasión al compilador (en el otro caso se indicaba explícitamente en el Makefile) que el código es ISO C++11 se debe acceder a la configuración de las herramientas de construcción del binario (Project -> Properties -> C/C++ Build ->Settings: Pestaña «Tool Settings»). Allí, hay una entrada para seleccionar el «dialecto» que debe entender el compilador, donde habrá que seleccionar ISO C++11 (-std=c++0x) o el que corresponda.

    Referencias:
    C++11 standard library indexing fails, __cplusplus recognized with wrong value

  • Genial página con consultas «comunes» para MySQL

    http://www.artfulsoftware.com/infotree/queries.php

     

  • Instalar entorno de desarrollo Qt4 en chroot 32 bits

    Para instalar el entorno de desarrollo de Qt4 en el chroot creado en el post Eclipse en chroot 32 bits se descarga desde Qt for Open Source C++ development on Linux/X11 el fichero con los fuentes de Qt4 para Linux/X11 y se instala el paquete libxext-dev (en Requisitos de instalación de Qt para X11 se dan más detalles y otras dependencias ya resueltas o no imprescindibles en este caso):

    dchroot -d
    sudo apt-get install libxext-dev libxrender-dev libglib2.0-dev

    Despues se descomprime el fichero de Qt obtenido (qt-everywhere-opensource-src-4.7.3.tar.gz cuando se hizo esto) y desde el directorio creado se ejecuta la siguiente orden (siempre dentro del entorno chroot):

    ./configure -prefix /usr/local/share/qt4 -developer-build -platform linux-g++-32 -glib

     

    Se acepta la correspondiente licencia comercial o de código abierto, según se desee, y se comienza la compilación e instalación:

    make && sudo make install

     

    Ahora, se obtiene desde el sitio de descarga Qt Eclipse Integration for C++ el plugin de Qt para Eclipse para Linux 32 bits (el construido sobre Qt 4.6.1), y se descomprime en el directorio de la instalación de Eclipse. Una vez hecho se lanza una primera vez Eclipse limpiando su cache para evitar problemas:

    eclipse -clean

     

    Y listo.