1. Actualizar un contador en un modelo en Django

    A veces necesitamos realizar algún tipo de operación aritmética sencilla en el modelo, y queremos que persista en la base de datos. Un ejemplo común es un contador, que queremos incrementar o decrementar en determinadas circunstancias.

    Una solución sencilla sería hacerlo directamente en Python, algo como esto:

    product = Item.objects.get(pk=4523)
    product.stock += 1
    product.save()
    

    Funciona perfecto, pero tiene dos problemas: Uno es una posible condición de carrera, si tenemos la mala suerte de que otro proceso está cambiando el valor del contador stock justo durante el intervalo entre que hemos leído los datos de la base de datos y el momento es que volvemos a salvarlos.

    El segundo problema es otro aspecto del mismo, estamos haciendo dos consultas a la base de datos, una para obtener el valor antiguo y otra para guardar el valor nuevo.

    Podemos resolver ambos problemas (Ya que los dos tienen la misma base) con un único paso, que realizará de forma atómica la propia base de datos, usando lo que Django llama expresiones F. Las expresiones F nos permiten realizar una actualización de un campo con valores relativos otros campos de la tabla.

    Para el ejemplo anterior, la solución mejorada podría ser:

    from django.db.models import F
    
    product = Item.objects.get(pk=4523)
    Item.stock = F("stock”) + 1
    product.save(update_fields=['stock'])
    

    El resultado sería una única interacción con la base de datos, que ejecutaría un código SQL similar al siguiente :

    UPDATE Item
       SET stock = stock + 1
     WHERE id = 4523;
    
    0

    Añadir un comentario

  2. Dos añitos sin publicar nada... Igual debería retomar esto
    0

    Añadir un comentario

  3. 0

    Añadir un comentario

  4. Los primeros 8 bytes de un fichero PNG son siempre los mismos. En decimal, serían:


        137 80 78 71 13 10 26 10
    

    Para poder examinar esos 8 bits en linux/unix, podemos usar la utilidad head, y con hexdump podemos verlos en hexadecimal


        head --bytes=8 ejemplo.jpg | hexdump -C
    

    El resultado debería ser:


    00000000  89 50 4e 47 0d 0a 1a 0a                           |.PNG....|
    00000008
    

    Buscar archivos PNG con otra extensión


    El siguiente codigo en Python 3 localiza archivos de tipo PNG sin extensión o con una extensión que no sería la correcta:


        import os
        PNG_SIGNATURE = b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a'
    
        for (path, dirnames,filenames) in os.walk('/home/jileon'):
            for filename in filenames:
                full_name = os.path.join(path, filename)
                if os.access(full_name, os.R_OK):
                    with open(full_name, 'rb') as f:
                        head = f.read(8)
                        if head == PNG_SIGNATURE:
                            if not full_name.lower().endswith('.png'):
                                print(full_name)
    
    0

    Añadir un comentario

  5. Supongamos una función muy sencilla, pero correctamente comentada:

    def suma(a, b):
        '''Esta función acepta dos parámetros, y devuelve la suma de ellos.
        
        Ejemplo:
        
        >>> result = suma(2, 4)
        >>> assert result == 6
        '''
        return a + b
        
    
    Vemos que funciona perfectamente:

    suma(2,390)
    
    392
     
    Si pedimos información de la función con help, nos da la documentación que incluimos como docstring:

    help(suma)
    
    Help on function suma in module __main__:
    
    suma(a, b)
        Esta función acepta dos parámetros, y devuelve la suma de ellos.
        
        Ejemplo:
        
        >>> result = suma(2, 4)
        >>> assert result == 6
    
    
    Vamos ahora a decorarla con un decorador muy sencillo, simplemente escribe a consola cuando llamamos a la función y, después de ejecutarla, imprime también el resultado:

    def log(functor):
        def wrapper(*args, **kwargs):
            print('Llamando a la función: {}'.format(functor.__name__))
            result = functor(*args, **kwargs)
            print('Resultado: {}'.format(result))
            return result
        return wrapper
    
    @log
    def suma(a, b):
        '''Esta función acepta dos parámetros, y devuelve la suma de ellos.
        
        Ejemplo:
        
        >>> result = suma(2, 4)
        >>> assert result == 6
        '''
        return a + b 
     
    Vemos que el decorador funciona correctamente:

    suma(2,3)
    
    Llamando a la función: suma
    Resultado: 5
    
    5
     
    El problema es que la nueva función suma ha perdido todo los metadatos de la función original, entre ellos la documentación:

    help(suma)
    suma.__name__
    
    Help on function wrapper in module __main__:
    
    wrapper(*args, **kwargs)
    
    
    'wrapper'
     
    El decorador wraps del módulo functools lo que hace es resolver este problema, inyectando en la función decorada todos los metadatos de la función original, entre ellos la docstring (Por eso necesita como parámetro la función original).

    import functools
    
    def log(functor):
        
        @functools.wraps(functor)
        def wrapper(*args, **kwargs):
            print('Llamando a la función: {}'.format(functor.__name__))
            result = functor(*args, **kwargs)
            print('Resultado: {}'.format(result))
            return result
        return wrapper
    
    @log
    def suma(a, b):
        '''Esta función acepta dos parámetros, y devuelve la suma de ellos.
        
        Ejemplo:
        
        >>> result = suma(2, 4)
        >>> assert result == 6
        '''
        return a + b 
     
    Vemos que todo sigue funcionando igual...
    suma(2,3)
    
    Llamando a la función: suma
    Resultado: 5
    
    5
     
    Pero la función help muestra la documentación de la función original...

    help(suma)
    
    Help on function suma in module __main__:
    
    suma(a, b)
        Esta función acepta dos parámetros, y devuelve la suma de ellos.
        
        Ejemplo:
        
        >>> result = suma(2, 4)
        >>> assert result == 6
     
    Escrito en colaboración con Enrique Cordobés Faura.
    0

    Añadir un comentario

  6. Nota: La versión más actualizada de este documento está siempre en

    https://github.com/euribates/Charla-DSL/blob/master/Ejemplo-uso-pyparsing.ipynb

    Ejemplo de uso de pyparsing

    Vamos a utilizar pyparsing para procesar un fichero csv. Para hacer las cosas más interesantes, supondremos que el formato de los ficheros es muy laxo; por ejemplo, podemos encontrarnos con lo siguiente:
    • Los campos de texto pueden venir entrecomillados o no
    • Los campos de tipo fecha pueden venir en dos formatos: YYYY-MM-DD o DD/MM/YYYY
    • Los campos de tipo booleano pueden venir con los valores 1SY o True para el valor lógico verdadero, y 0NF y False para el valor lógico falso
    • Los números decimales pueden venir con una coma o con un punto como separador decimal.
    • La primera línea contiene los nombres de los campos, el resto los datos; en los dos casos se separa cada valor por el caracter ;

    Ejemplo de datos a procesar

    Algo como esto:
    Comentario;Activo;Fecha;Importe
    Texto sin comillas porque yo lo valgo;Y;2017-08-08;4292.00
    "Ahora si que pongo comillas";F;25/8/2014;3200.00
    Ya ves, todo vale;True;1/1/1970;4532,02
    

    Una gramatica para estos ficheros

    La gramática podría ser algo así:
    root -> header + lines
    header -> \.+ # ignorar la linea 
    lines -> line+
    line -> text + sep + date + sep + bool + sep + cost
    text -> '"' + literal + '"' | literal
    date -> year + '-' + month + '-' + day | day + '/' + month + '/' + year
    cost -> \d+[\.|,]\d{2}
    bool -> '1' | 'S' | 'Y' | 'T' | 'True' | '0' | 'N' | 'F' | 'False'
    year -> \d{4}
    month -> 1|2|3|4|5|6|7|8|9|10|11|12
    day -> \d{1,2}
    

    Implementación con pyparsing

    Las gramáticas pueden ser un poco intimidantes la primera vez que las ves. Lo bueno de pyparsing es que nos permite testear y modificar las distintas partes de la gramática como piezas sueltas. Así podemos crear el parser poco a poco, ensamblando las distinas piezas, con la confianza de que estas funcionan.

    Parseando fechas

    Por ejemplo, para las fechas, que pueden venir en dos formatos, tenemos el siguiente fragmento de la gramática (pasado a la sintaxis de pyparsing, y por tanto cambiando el orden, la regla inicial sería la última):
    In [2]:
    from pyparsing import Literal, Regex, oneOf, StringEnd, Group, ParseException
    dash = Literal('-')
    slash = Literal('/')
    year = Regex('\d{4}')
    month = Regex('\d{1,2}')
    day = Regex('\d{1,2}')
    date = year + dash + month + dash + day ^ day + slash + month + slash + year
    # pyparsong sobrecarga el operador ^ para indicar alternancia
    La clase Literal sirve para indicar una expresión o token literal, que queremos detectar durante la fase de parseo. Así, definimos dash y slash para detectar los literales - y /. La clase Regex nos permite definir tokens usando expresiones regulares. Estos objetos, así como el resto de los que veremos, derivan de la clase ParserElement de pyparsing, que sobrecarga varios operadores para poder expresar las reglas de la gramática. Así, la regla:
    date -> year + '-' + month + '-' + day | day + '/' + month + '/' + year
    
    Se puede expresar en Python con los operadores + y ^:
    date = year + dash + month + dash + day ^ day + slash + month + slash + year
    
    Aparte de cambios como el uso del operador ^, o la definición de los literales dash y slash, podemos ver que la gramática se mapea de forma casi directa a expresiones Python.
    Veamos que tal funciona este mini-parser:
    In [3]:
    try:
        date.parseString('hola')
    except ParseException:
        pass  # Ok, no es una fecha
    print(date.parseString('25/8/2016'))
    print(date.parseString('2017-12-08'))
    ['25', '/', '8', '/', '2016']
    ['2017', '-', '12', '-', '08']
    
    pyparsing define su propia clase de excepciones para errores de Parseo, parseException. Si nuestro parser es muy complicado puede ser interesante usar esta misma excepción para indicar nuestros propios errores. Por ejemplo, ahora mismo aceptamos para el día cualquier combinación de dos dígitos, e igualmente para el mes:
    In [4]:
    print(date.parseString('99/88/2016'))  # opps, esto no debería valer, pero vale
    ['99', '/', '88', '/', '2016']
    
    Más adelante veremos que podemos tratar estos casos y elevar errores explicativos que provoquen el fallo del parser.
    Por ahora, poco más de lo que podríamos hacer simplemente con expresiones regulares.
    Podemos realizar una pequeña mejora. Observemos un detalle de los separadores usados en el formato de fechas, definidos como dash y slash; en realidad, sus valores no nos interesan. Podemos calcular el valor de la fecha sin necesidad de saber que caracteres se usaron como separador. Estos elementos son necesarios para el parser, pero no tienen más utilidad.
    Existe una clase en pyparsing llamada Suppress que funciona exactamente igual que Literal, pero que retira el token, de forma que nos evitamos procesarlo. Cambiemos la gramática para redefinir dash y slash usando Suppress en vez de Literal:
    In [5]:
    from pyparsing import Suppress
    dash = Suppress('-')
    slash = Suppress('/')
    year = Regex('\d{4}')
    month = Regex('\d{1,2}')
    day = Regex('\d{1,2}')
    date = year + dash + month + dash + day ^ day + slash + month + slash + year
    In [6]:
    print(date.parseString('23/9/2016'))  # bien, el separador desaparece
    ['23', '9', '2016']
    
    No está mal, pero la mejora realmente interesante sería que nos devolviera algo más elaborado, un objeto de tipo fecha, objetos de tipo datetime.datetime, por ejemplo. Vamos a ello. Para eso, necesitamos usar las reglas de parseo.

    Reglas de parseo

    Podemos asociar acciones a las reglas de parseo (en este daso, date) para que se ejecuten cada vez qe se active la regla.
    Vamos a asociar una función que no haga nada, solo imprimir un valor para ver que, efectivamente, se ejecuta cuando la regla de parseo se activa. Usaremos el método setParseAction:
    In [7]:
    def very_simple_action():
        print('OK, se ha ejecutado la acción')
        
    date.setParseAction(very_simple_action)
    print(date.parseString('25/8/2016'))
    print(date.parseString('2017-12-08'))
    OK, se ha ejecutado la acción
    ['25', '8', '2016']
    OK, se ha ejecutado la acción
    ['2017', '12', '08']
    
    De la documentación de pyparsing, podemos obtener más información sobre como definir y usar estas acciones:
    Podemos definir uno o varias acciones a realizar cuando se produce una coincidencia que activa la regla del parser. Estas acciones pueden ser cualquier objeto de tipo callable de python; es decir, funciones, métodos u objetos instanciados de clases que definan el método mágico __call__.
    Las acciones pueden aceptar desde cero hasta tres argumentos, es decir que, dependiendo de como definamos la acción fn, esta será llamada como fn()fn(toks)fn(loc, toks) o fn(s, loc, toks). El significado de estos parámetros es el siguiente:
    • s: es la string original que activó el patrón de la regla
    • loc: es la localización, dentro del texto, de la substring s (Útil para generar mensajes de error)
    • toks: Una lista de los tokens encontrados, empaquetados en forma de objeto de tipo ParseResults
    Si la función quiere modificar los tokens, debe devolver un nuevo valor como resultado de la función, con lo que la lista de tokens devueltos reemplazaría a la original. Si no queremos realizar ningún cambio, la función no debe retornar ningún valor.
    Definamos una acción, solo para ver que estos parámetros se pasan efectivamente:
    In [8]:
    def I_just_wanna_see(s, loc, tokens):
        print('s:', s)
        print('loc:', loc)   
        print('tokens:', tokens)
        print()
        
    date.setParseAction(I_just_wanna_see)
    print(date.parseString('25/8/2016'))
    s: 25/8/2016
    loc: 0
    tokens: ['25', '8', '2016']
    
    ['25', '8', '2016']
    
    Nota: Podemos asignar varias acciones usando el método addParseAction. en ese caso, las acciones se ejecutan de forma anidada, siendo la primera en ejecutarse la primera en añadirse. Cada acción recibe como entrada el resultado de la anterior y pasa su resultado a la siguiente. Veamos un ejemplo:
    In [9]:
    token = Literal('hola')
    token.addParseAction(lambda tokens: 'ei' + tokens[0] + 'ai')
    token.addParseAction(lambda tokens: tokens[0].upper())
    print(token.parseString('hola'))
    ['EIHOLAAI']
    
    Si cambiamos el orden en que se añaden las acciones, el resultado puede diferir, lógicamente:
    In [10]:
    token = Literal('hola')
    token.addParseAction(lambda tokens: tokens[0].upper())
    token.addParseAction(lambda tokens: 'ei' + tokens[0] + 'ai')
    print(token.parseString('hola'))
    ['eiHOLAai']
    
    Con esto ya podemos definir una acción que nos devuelva un objeto date. Usaremos una acción con un solo parámetro, tokens, la lista de los tokens detectados, ya que no necesitamos los otros parámetros.
    Como devolvemos un valor, el parser sustituirá la lista de tokens detectados por ese nuevo valor.
    In [11]:
    import datetime
    def get_as_date(tokens):
        first_element = tokens[0]
        if len(first_element) == 4:  # Formato YYYY-MM-DD
            d = datetime.date(
                int(tokens[0]),  # Year
                int(tokens[1]),  # Month
                int(tokens[2]),  # Day
                )
        else:
            d = datetime.date(
                int(tokens[2]),  # Year
                int(tokens[1]),  # Month
                int(tokens[0]),  # Day
                )
        return d
        
    date.setParseAction(get_as_date)
    print(date.parseString('25/8/2016'))
    print(date.parseString('2017-12-08'))
    [datetime.date(2016, 8, 25)]
    [datetime.date(2017, 12, 8)]
    

    Vamos a parsear valores lógicos

    Podemos hacer algo similar con los objetos booleanos. Usaremos una de las funciones auxiliares de pyparsing, oneOf, que nos permite definir de forma rápida un conjunto de literales alternativos. Además se asegura de que siempre intentará capturar el literal más grande, en caso de que haya conflicto entre alguno de ellos; por ejemplo, entre < y <= primero intentará encontrar una correspondencia con el más largo, <=, y si no la encuentra lo intentará con <.
    In [12]:
    from pyparsing import oneOf
    boolean = oneOf('1 S Y T True 0 N F False')
    print(boolean.parseString('True'))
    ['True']
    
    Con un poco de mágia en forma de acción asociada a la regla obtendremos valores booleanos de Python. LA API de pyparsing es fluida, por lo que podemos definir la regla y asociar la acción en una sola línea:
    In [13]:
    def get_as_bool(tokens):
        return tokens[0] in ('1', 'S', 'Y', 'T', 'True')
    boolean = oneOf('1 S Y T True 0 N F False').setParseAction(get_as_bool)
    # simple tests
    assert boolean.parseString('1').pop() is True
    assert boolean.parseString('S').pop() is True
    assert boolean.parseString('Y').pop() is True
    assert boolean.parseString('T').pop() is True
    assert boolean.parseString('True').pop() is True
    assert boolean.parseString('0').pop() is False
    assert boolean.parseString('N').pop() is False
    assert boolean.parseString('F').pop() is False
    assert boolean.parseString('False').pop() is False

    Parsear los importes

    Nos queda el problema de los importes, que pueden usar como separador decimal la coma, al estilo español, o el punto, al estilo internacional, y las cadenas de textos, que pueden venir limitadas por comillas o no. Los dos casos son fáciles de tratar:
    In [14]:
    from pyparsing import nums, Word
    cost = Word(nums) + oneOf('. ,') + Regex('\d\d')
    cost.parseString('3819.24')
    Out[14]:
    (['3819', '.', '24'], {})
    La clase Word nos permite definir una palabra, pasandole uno o dos parámetros, que son vocabularios. Los vocabularios se pueden indicar con una string de símbolos, como 'aeiou'nums es solo una constante definida en pyparsing que vale '0123456789'. Podiamos haber usado una expresión regular, pero Word es bastante interesante.
    Si a Word se le pasa un solo vocabulario, define una palabra como una secuencia de n caracteres tomados de los símbolos definidos en el vocabulario.
    Si se le pasan dos vocabularios, define una palabra como una secuencia donde el primer caracter debe pertenecer al primer vocabulario y el resto, si los hubiera, al segundo. Por ejemplo, podemos definir un parser para los nombres válidos de Python, que permiten el uso de carateres alfanuméricos y el carácter subrayado, pero no no se permiten que empiece por un dígito:
    In [15]:
    from pyparsing import alphas, nums, alphanums
    assert alphas == 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    assert alphanums == alphas + nums
    var_name = Word(alphas + '_', alphanums + '_')
    var_name.parseString('a')
    var_name.parseString('a1')
    var_name.parseString('alp_ha')
    try:
        var_name.parseString('1uno')
    except ParseException:
        # Oops, no se permite el caracter '_'
        pass
    Definamos otra patrón, incluyendo el símbolo dolar como caracter válido al principio, PERL-style:
    In [16]:
    var_name_plus = Word(alphas + '$', alphanums + '_')
    assert var_name_plus.parseString('a').pop() == 'a'
    assert var_name_plus.parseString('$_alpha').pop() == '$_alpha'
    assert var_name_plus.parseString('$Alpha_plus').pop() == '$Alpha_plus'
    Hecha esta disgresión, volvamos al problema de tratar los importes. Añadamos una acción para obtener un número de tipo Decimal:
    In [17]:
    from decimal import Decimal
    def get_as_decimal(tokens):
        int_part, dec_part = tokens
        return Decimal('{}.{}'.format(int_part, dec_part))
    cost = Word(nums) + Suppress(oneOf('. ,')) + Regex('\d\d')
    cost.setParseAction(get_as_decimal)
    cost.parseString('484432,23')
    num_esp = cost.parseString('484432,23').pop()
    num_int = cost.parseString('484432.23').pop()
    assert num_esp == num_int == Decimal('484432.23')
    print(cost.parseString('3.14'))
    [Decimal('3.14')]
    

    Cadenas de texto con o sin delimitador

    Para poder procesar las cadenas de texto, ignorando si procede las comillas delimitadoras opcionales, podemos hacer:
    In [18]:
    quote = Suppress('"')
    content = Regex("[^\";]+") # Cualquier secuencia de caracteres, excepto ; y "
    text = quote + content + quote ^ content
    assert text.parseString('Texto sin comillas').pop() == 'Texto sin comillas'
    assert text.parseString('"Texto con comillas"').pop() == 'Texto con comillas'
        
    Bueno, ha sido un viaje un poco largo, con paradas en algunos puntos interesantes, pero ya podemos escribir la gramática completa, junto al parser y las acciones aplicadas:
    In [19]:
    from pyparsing import OneOrMore
    # Funciones de conversion
    def get_as_decimal(s, lok, tokens):
        int_part, _sep, dec_part = tokens
        return Decimal('{}.{}'.format(int_part, dec_part))
    def get_as_date(s, loc, tokens):
        a, b, c = tokens
        if len(a) == 4:  # Formato YYYY-MM-DD
            return datetime.date(int(a), int(b), int(c))
        else:  # Formato DD/MM/YYYY
            return datetime.date(int(c), int(b), int(a))
    def get_as_bool(s, loc, tokens):
        return tokens[0] in ('1', 'S', 'Y', 'T', 'True')
    sep = Suppress(';')
    quote = Suppress('"') # Texto
    content = Regex("[^\";]+") # Cualquier secuencia de caracteres, excepto ; y "
    text = quote + content + quote ^ content
    text.setParseAction(lambda tokens: tokens[0].strip())
    boolean = oneOf('1 S Y T True 0 N F False') # Valores lógicos
    boolean.setParseAction(get_as_bool)
    dash = Suppress('-') # Fechas
    slash = Suppress('/')
    year = Regex('\d{4}')
    month = Regex('\d{1,2}')
    day = Regex('\d{1,2}')
    date = year + dash + month + dash + day ^ day + slash + month + slash + year
    date.setParseAction(get_as_date)
    cost = Word(nums) + oneOf('. ,') + Regex('\d\d')  # Importes
    cost.setParseAction(get_as_decimal)
    line = Group(text + sep + boolean + sep + date + sep + cost)  # One Line
    lines = OneOrMore(line)  # Lines
    lines.setParseAction(lambda tokens: list(tokens))
    header = Suppress(Regex('.+'))  # Header
    parser = header + lines + StringEnd() # First rule
    • La clase OneOrMore nos permite implementar las reglas de una secuencia de uno o más elementos repetidos, como su mismo nombre indica. Pyparsing define muchos más clases de este tipo, como ZeroOrMoreOptional, (Uno o cero), OnlyOne...
    • La clase Group nos permite agrupar varios tokens en un solo resultado, normalmente porque vamos a tratarlos todos juntos.
    • La clase StringEnd nos permite indicar que el parser, al consumir este token, debería de haber terminado, es decir, que todo el texto a parserar debe consumirse íntegramente.
    Vamos a hacer unas pruebas parseando líneas individuales:
    In [20]:
    print(line.parseString('Texto sin comillas porque yo lo valgo;Y;2017-08-08;4292.00'))
    [['Texto sin comillas porque yo lo valgo', True, datetime.date(2017, 8, 8), Decimal('4292.00')]]
    
    In [21]:
    print(line.parseString('"Ahora si que pongo comillas";F;25/8/2014;3200.00'))
    [['Ahora si que pongo comillas', False, datetime.date(2014, 8, 25), Decimal('3200.00')]]
    
    In [22]:
    print(line.parseString('Ya ves, todo vale;True;1/1/1970;4532,02'))
    [['Ya ves, todo vale', True, datetime.date(1970, 1, 1), Decimal('4532.02')]]
    
    print(header.parseString('Comentario;Activo;Fecha;Importe'))  # Ignoramos la cabecera
    Y la prueba de fuego, un fichero completo:
    In [23]:
    source = '''Comentario;Activo;Fecha;Importe
    Texto sin comillas porque yo lo valgo;Y;2017-08-08;4292.00
    "Ahora si que pongo comillas";F;25/8/2014;3200.00
    Ya ves, todo vale;True;1/1/1970;4532,02
    '''
    g = parser.parseString(source)
    for item in g:
        print(item)
    ['Texto sin comillas porque yo lo valgo', True, datetime.date(2017, 8, 8), Decimal('4292.00')]
    ['Ahora si que pongo comillas', False, datetime.date(2014, 8, 25), Decimal('3200.00')]
    ['Ya ves, todo vale', True, datetime.date(1970, 1, 1), Decimal('4532.02')]
    
    Unsado el método parseFile podemos procesar un fichero, si especificamos el nombre, un fichero abierto, o cualquier objeto que implemente una interfaz similar a File:
    In [24]:
    with open('ejemplo.csv', 'r') as stream:
        g = parser.parseFile(stream)
        for item in g:
            print(item)
    ['Texto sin comillas porque yo lo valgo', True, datetime.date(2017, 8, 8), Decimal('4292.00')]
    ['Ahora si que pongo comillas', False, datetime.date(2014, 8, 25), Decimal('3200.00')]
    ['Ya ves, todo vale', True, datetime.date(1970, 1, 1), Decimal('4532.02')]
    ['Los espacios no son problema', False, datetime.date(2017, 2, 22), Decimal('1203.23')]
    

    Ventajas de Pyparsing

    - **Robusto y sencillo de usar**. Pyparsing lleva más de una década de desarrollo, y se basa en el uso de gramáticas para la definición formal de lenguajes. El paso de la gramática a código Python es casi directo.
    - **Desarrollo incremental, facilmente testeable**. El parser final se puede ir construyendo paso a paso.
    - No se ve en los ejemplo, pero podemos añadir **validaciones y mensajes de error explicativos** que simplifican la resolución de problemas --incluyendo, por ejemplo, número de línea y posición del error. Podemos asignar nombres a los resultados de los tokens para que seán más sencillos de referencias, y muchas otras funcionalidades que no hemos podido ver aquí.
    - **Flexible**, comparado con un parser hecho a mano o en base a un montón de espresiones regulares. A modo de ejemplo, véanse los **ejercicios para el lector**.

    Ejercicios para el lector

    • Añadir otro formato válido para las fechas, por ejemplo, 10/abr/2017 (10 puntos)
    • Permitir que la última columna, el importe, acepte también un valor entero, es decir, sin parte decimal (20 puntos)
    • Permitir que los textos puedan venir sin comillas, con comillas simples o con comillas dobles (20 puntos)
    • El jefe ha modificado el formato, el fichero tiene ahora una última ĺinea donde va el total acumulado de todos los importes previos, algo así:
        Comentario;Activo;Fecha;Importe
        Texto sin comillas porque yo lo valgo;Y;2017-08-08;4292.00
        "Ahora si que pongo comillas";F;25/8/2014;3200.00
        Ya ves, todo vale;True;1/1/1970;4532,02
        12024.02
      
      El parser debe adaptarse a este cambio, y comprobar que la suma de los importes coincide con el dato final. Si, ya sé que, técnicamente, esto ha dejado de ser un CSV. Estas cosas pasan. (100 puntos y una gran satisfacción personal)

    Mas información

    0

    Añadir un comentario

  7. El próximo 1 de junio, jueves, a partir de las 18:00 tendremos un nuevo encuentro de desarrolladores para hablar de nuestros temas favoritos: desarrollo, unicornios y cervezas. En esta ocasión tendremos tres charlas:
    • AWS: Desplegando Python en Amazon
    • PyTesting e integración continua con Travis
    • Vue.js: El framework javascript para muggles
    Tras las cuales tendremos la segunda parte: networking, vida social, risas y ping pong. Todo ello en las instalaciones del Equipo Para (C/La Marina, 4, Santa cruz de Tenerife).
    Por favor, confirmad la asistencia en el meetup, para poner las cervezas a enfriar.
    https://www.meetup.com/Agile-Canarias/events/235289799/
    Esta vez nos hemos venido arriba y tenemos hasta cartel promocional:
    0

    Añadir un comentario

  8. El 11 de marzo tendremos el primer PyDay en Canarias.Un PyDay es una jornada de un día completo sobre el lenguaje de programación Python, con el formato de unas conferencias pero un poco más dinámico. La cita es en la Sala de Estudio Caja Canarias Campus Anchieta. Como el foro es limitado, debéis inscribiros en esta página, pero, por favor, hacedlo sólo si vais a asistir seguro, si no le estareis quitando la plaza a otros ser humano interesado.

    Primer PyDay tenerife 2017

    En este caso constará de dos tracks independientes, uno de talleres introductorios y otro de charlas más avanzadas.

    Para los alumnos de la Universidad de La Laguna, la asistencia es compatible con un crédito ECTS.

    En la web podéis consultar todo el programa del próximo PyDay Tenerife.

    Por último, si promocionas el evento por tus redes sociales, te agradeceríamos mucho si incluyeras el Hashtag #PyDayTf.

    0

    Añadir un comentario

  9. Un año más llegamos a la IX edición de este -a veces fatalmente incomprendido- llamamiento a la paz, la esperanza y, por que no decirlo, el amor conocido como el Big Culo Day. Esta vez toca un clásico europeo, Natasha, La azafata de maravillosas formas dibujada por François Walthér, y que ahora esta siendo recuperada en una serie de integrales de Dolmen.

    0

    Añadir un comentario

  10. ¿Cómo probar las páginas de errores (404, etc...) en desarrollo?

    Para ver esas páginas de error, hay que desactivar la opción DEBUG de la configuración. Pero si lo hacemos, los contenidos estáticos dejan de servirse, así que no podemos estar seguros de como se verán las páginas finales, a no ser que instalemos nuestro propio servidor de contenidos estáticos.
    O, por otro lado, podemos usar el flag insecure al llamar a runserver:
    manage.py runserver --insecure
    

    Esto obliga al servidor de desarrollo a servir los ficheros a partir de los directorios static de las aplicaciones, aunque la variable settings.DEBUG sea False.

    De ninguna manera debemos usar este truco para poner en explotación un servidor de desarrollo de Django. Citando la documentación oficial:

    [...] By using this you acknowledge the fact that it’s grossly inefficient and probably insecure. This is only intended for local development, should never be used in production [...]

    ¿Cómo analizar las consultas SQL que está realizando Django?

    Normalmente el primer paso para poder optimizar Django consiste en analizar el número de consultas, así como los tiempos de ejecución de las mismas. Para ello hay una extensión muy recomendable: Django-debug-toolbar.

    The Django Debug Toolbar is a configurable set of panels that display various debug information about the current request/response and when clicked, display more details about the panel’s content.
    Lo ideal es poner esta app en desarrollo, y no en el despliegue final. Véase la siguiente entrada

    ¿Cómo tener diferentes entornos de desarrollo/explotación/pruebas?

    Mi solución actual, adaptada del muy recomendable libro Two Scoops of Django, consiste en tener un settings.py que será el que se use en despliege y luego un fichero development.py, que simplemente importa todo el contenido del settings.py y realiza las modificaciones que crea oportunas.
    Por ejemplo:

    from main.settings import *
    
    DEVELOPMENT = True
    
    INSTALLED_APPS += (
        'debug_toolbar',
        )
    
    Para arrancar en desarrollo uso:
    manage.py runserver --settings=main.development
    

    ¿Cómo hago para que mi método booleano se vea bonito en el admin?

    Esta documentado, pero a menudo resulta complicado de encontrar. Si escribimos un método de un modelo que devuelve solo True o False, y lo consultamos en el admin, este nos muestra texto. Sin embargo, para campos definidos como booleanos (BooleanField) nos muestra un icono. Podemos hacer que utilice esos mismos iconos si añadimos un atributo boolean al método.
    por ejemplo:
    def nacio_en_bisiesto(self):
        year = self.birthday.year
        return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
    
    nacio_en_bisiesto.boolean = True
    

    ¿Cómo hago para mostrar mi propio contenido html en el admin?

    Para que el admin interprete cualquier texto producido por un método como Html, sin escaparlo, debemos asignarle al método en cuestión el atributo allow_tag a True. Es recomendable que nos escudemos de posibles fallos de seguridad usando la función format_html() siempre que incluyamos en la salida texto generado por el usuario final.
    Por ejemplo:

    def colored_name(self):
        return format_html('<span style="color: #{};">{} {}</span>',
                        self.color_code,
                        self.first_name,
                        self.last_name)
    
    colored_name.allow_tags = True
    
    0

    Añadir un comentario

Archivo del blog
Etiquetas
Etiquetas
Enlaces interesantes
Cargando