Facundo Batista: Películas, duda, series

De nuevo vi bastante más que las que anoté para ver. Pero no le estoy dedicando mucho tiempo a ver películas (aunque aproveché bastante los vuelos largos en el último viaje), con lo cual creo que lo que pasa es que estoy anotando pocas nuevas.

¿Por qué? Creo que porque no hay mucho que me interese de lo que está saliendo (por ejemplo, ignoro todo lo que sean películas basadas en guerra, o de terror "normal" (o sea, aburrido), o la trigésimo novena de los avengers, etc...).

¿O quizás estoy perdiéndome de ver trailers que están buenos? ¿Hay películas que están por salir que piensan que están buenas y NO estoy anotando? ¿Mejores lugares para buscar trailers que IMDb? Propongan.

Por otro lado, estuve arrancando y cerrando un par de series. Recomiendo fuerte Borgen, enterita. Es muy buena. La primera temporada de Fargo es genial, la segunda pinta muy bien (estoy en eso). También estoy viendo Girls, que zafa. Y para que vean que no son todas rosas, tenía bajadas para ver y no pude pasar del primer capítulo (y picoteos de otros capítulos, para asegurarme de no llevarme una impresión equivocada) de Flight of the Conchords (no es mi tipo de humor, no me da nada, muy aburrida) y Dark Net (odio las series medio en primera persona onda "yo soy un muchacho tranquilo, crecí en oklahoma, conocí mi chica online" mientras lo muestran llevarle pasto a una vaca o una boludez así).

Bueno. A los bifes...

  • Absolutely Anything: +0. Una idea no nueva, pero encarada de un lado interesante y divertida.
  • Atomic Blonde: +0. No deja de ser una de espías en la época de la guerra fría, pero atrapa, divierte, y convence (si es que te interesa ese tipo de películas).
  • Atomica: -0. El contexto y parte de la historia eran interesantes, pero las actuaciones bleh, la peli en general no suma.
  • Black Snow: -0. La historia zafa y las actuaciones están buenas, pero es muy lenta por partes, y no te deja gran cosa.
  • Elle: -0. Es una historia chata, en el sentido que va contando cosas que poquito a poquito suma un relato, pero nunca te das cuenta cual es el nudo de la cuestión, entonces como que no vas entendiendo mucho, pero algo se forma; más allá de eso, no me entusiasmó nada.
  • Frank & Lola: +0. La película está bien, no es gran cosa pero cierra y se pasa un buen rato. Compensan que sí me gustan las películas "de chefs" y que no me gustan las películas donde las parejas tienen relaciones tóxicas.
  • Future Man: No es una peli, sino una serie! Ahí la reencolé donde corresponde, sacándola de acá.
  • MindGamers: -0. Tiene partes muy interesantes, pero el estilo de filmación la hace medio insoportable, y aunque una notable suma de sinsentidos parece acomodarse luego, no llega a funcionar.
  • Murder on the Orient Express: -0. No me terminó de enganchar... No sé exactamente qué es lo que hizo que la peli se me haga muy anodina.
  • Passengers: +0. Divertida, con efectos interesantes, con una trama liviana pero dinámica y se la pasa bien.
  • Ready Player One: +0. Está buena por los efectos y por todas las referencias... luego la historia no me llamó demasiado la atención.
  • Rooster's Blood: +0. Casi la saco cuando habían pasado veinte minutos y la historia no apuntaba a ningún lado, pero terminó zafando y está buena. Hay que bancarse algunos planos lentos interminables, pero va.
  • The Assignment: -0. Me gustó la forma gráfica de contar y mostrar la historia, y la historia misma no está mal, con un par de vueltas que sorprenden y te llevan hasta el final. Pero está armada como si fuese el suceso épico del cine y se queda muy corta, y eso hace que le reste un montón.
  • The Autopsy of Jane Doe: +1. Con la base de la historia muy conocida (se trató miles de veces), la peli logra darle un giro totalmente "real" pero sorprendente, lo cual más allá de que la peli es en sí de terror, tiene ese misterio que te lleva hasta al final y vale la pena.
  • The Circle: +0. La historia zafa, los paralelos con la realidad son evidentes; es una muy buena película para que muchos tomen conciencia de a donde nos dirigimos.
  • The Comedian: -0. La historia zafa, pero no va a ningún lado, las actuaciones están bien, pero a la peli le falta como "consistencia"...
  • The Discovery: +0. El concepto base de la historia está bueno, la trama alrededor también, y la vuelta del final es inesperada. Está bien.
  • The Little Hours: +0. Muy divertida, muy atípica. Ligeramente basada en el Decamerón, es una excusa para hilar una historia que lleva bien la peli.
  • The Recall: -1. Es muy mala. Así y todo la terminé de ver, porque no es que haya algo que sea insoportable, sólo es muy mala de forma parejita :).
  • The Whole Truth: +0. Está bien, se nota que la trama es más compleja y está resuelta con la voz en off, pero se sigue bien y se arma todo bien, con un desenlace no esperado y que cambia un poco la perspectiva de lo anterior.
  • Unlocked: +0. No deja de ser una de espías/acción, pero las idas y vuelta que tiene la hacen interesante. Pierde un poco la gracia el final como apostando a secuelas, pero bueh, la primera zafa, si hacen una continuación no me anoto...
  • What Happened to Monday: +0. Muy rara, pero entretenida. Lo que labura la actriz con los múltiples papeles es loable.
  • Zero Days: -1. No la pude terminar de ver de lo embolante que era. Hay toda una parte interesante que es, justamente, el tema del virus. Y todo lo relacionado con la planta atómica también está bueno. Pero si estirás eso a casi dos horas de película, encima en ese formato de "entrevistas", se hace insoportable.
Jane Doe

De las pocas nuevas...

  • Hellboy: (2019; Action, Adventure, Fantasy, Sci-Fi) Based on the graphic novels by Mike Mignola, Hellboy, caught between the worlds of the supernatural and human, battles an ancient sorceress bent on revenge.::Dominic Henriott AND ABDUL [D: Neil Marshall; A: David Harbour, Milla Jovovich, Ian McShane]
  • Yesterday: (2019; Comedy, Drama, Fantasy, Music, Musical, Romance) A struggling musician realizes he's the only person on Earth who can remember The Beatles.::a_clockwork_alpaca [D: Danny Boyle; A: Ana de Armas, Lily James, Kate McKinnon]
  • All Inclusive: (2018; Comedy) Pablo and Lucia live together. Pablo buys an All-Inclusive in Brazil online as a surprise for Lucia but his boss firing him, so Pablo tries to cancel the trip but he can't. They travel anyway, but Brazil waits with nothing but trouble.. [D: Diego Levy, Pablo Levy; A: Alan Sabbagh, Julieta Zylberberg, Mike Amigorena]
  • Aniara: (2018; Drama, Sci-Fi) A spaceship carrying settlers to Mars is knocked off course, causing the consumption-obsessed passengers to consider their place in the universe. [D: Pella Kagerman, Hugo Lilja; A: Emelie Jonsson, Bianca Cruzeiro, Arvin Kananian]
  • Asher: (2018; Drama, Thriller) An aging hitman's last job goes sideways, forcing him to redeem himself. [D: Michael Caton-Jones; A: Famke Janssen, Ron Perlman, Richard Dreyfuss]
  • Brexit: (2019; Biography, Drama, History) Political strategist Dominic Cummings leads a popular but controversial campaign to convince British voters to leave the European Union from 2015 up until the present day. [D: Toby Haynes; A: Benedict Cumberbatch, Sarah Belcher, Malcolm Freeman]
  • Escape Room: (2019; Action, Adventure, Drama, Horror, Mystery, Sci-Fi, Thriller) Six strangers find themselves in a maze of deadly mystery rooms, and must use their wits to survive. [D: Adam Robitel; A: Taylor Russell, Logan Miller, Jay Ellis]
  • Green Book: (2018; Biography, Comedy, Drama, Music) A working-class Italian-American bouncer becomes the driver of an African-American classical pianist on a tour of venues through the 1960s American South. [D: Peter Farrelly; A: Viggo Mortensen, Mahershala Ali, Linda Cardellini]
  • Incontrol: (2017; Drama, Sci-Fi, Thriller) 4 university students hook up to a machine allowing them to become one of their fellow students - e.g. allowing them each to party as one of the rich and beautiful. The long hook-ups start seriously affecting their normal selves.::Scott Filtenborg [D: Kurtis David Harder; A: Sarah Troyer, Anja Savcic, Valerie Planche]
  • Pet Sematary: (2019; Horror, Thriller) Louis Creed, his wife Rachel, and their two children Gage and Ellie move to a rural home where they are welcomed and enlightened about the eerie 'Pet Sematary' located nearby. After the tragedy of their cat being killed by a truck, Louis resorts to burying it in the mysterious pet cemetery, which is definitely not as it seems, as it proves to the Creeds that sometimes, dead is better. [D: Kevin Kölsch, Dennis Widmyer; A: John Lithgow, Jason Clarke, Amy Seimetz]
  • Spider-Man: Into the Spider-Verse: (2018; Animation, Action, Adventure, Family, Sci-Fi) Teen Miles Morales becomes Spider-Man of his reality, crossing his path with five counterparts from other dimensions to stop a threat for all realities.::Chockys [D: Bob Persichetti, Peter Ramsey, Rodney Rothman; A: Shameik Moore, Jake Johnson, Hailee Steinfeld]
  • The Irishman: (2019; Biography, Crime, Drama, History, Thriller) A mob hitman recalls his possible involvement with the slaying of Jimmy Hoffa. [D: Martin Scorsese; A: Anna Paquin, Robert De Niro, Al Pacino]
  • Get Her... If You Can: (2019; Comedy) Roberto (Javier Rey) and Daniela (Amaia Salamanca) are siblings, both wealthy business people, with very different styles. Roberto's plot to return her sister her sense of humour gets unexpectedly out of hand. [D: Inés de León; A: Amaia Salamanca, Hugo Silva, Javier Rey]

Finalmente, el conteo de pendientes por fecha:

(Jun-2013)    2
(Sep-2013)    8
(Dic-2013)   12   4
(Abr-2014)    8   3
(Jul-2014)   10  10   5   1
(Nov-2014)   22  22  22   7
(Feb-2015)   13  13  13  10
(Jun-2015)   16  16  15  13  11   1
(Dic-2015)       21  19  19  18   6   1
(May-2016)           26  25  23  21   9
(Sep-2016)               19  19  18  14   1
(Feb-2017)                   26  25  23  21   9   1
(Jun-2017)                       23  23  21  18   5
(Dic-2017)                           19  19  18  16
(May-2018)                               22  22  22
(Sep-2018)                                   12  12
(Mar-2019)                                       13
Total:       91  89 100  94  97  94  89  84  79  69

Facundo Batista: PyCamp 2019

Se nos fue el PyCamp 2019, el doceavo, el primero en San Rafael, Mendoza. Más precisamente en Los Reyunos, en instalaciones de la Universidad Tecnológica Nacional.

El lugar estuvo bien, aunque no 100% preparado para cómo lo íbamos a usar, lo acondicionamos al toque y funcionó sin problemas. Era lejos a nivel viaje, pero estaba buenísimo, mucho verde, super cómodo, y aunque no estaba cerrado para nosotros, estábamos bastante aislados (excepto cuando nos metíamos en la pileta, claro, porque era compartida con gente que visitaba el predio para pasar el día). Creo que fue el PyCamp con mejor paisaje de toda la historia:

Más o menos el atardecer

Sí fue seguro el PyCamp donde la internet funcionó sin problemas (casi perfecto, más allá de un par de cortecitos).

Y teníamos el plus de que había un proyector que usamos un montón. Deberíamos analizar comprar un microproyector para que PyAr tenga en este tipo de eventos, porque es evidente que cuando se lo tiene, se lo aprovecha.

El pasto que se ve atrás de la pile está varios metros abajo

A nivel proyectos, le metí bastante tiempo a un experimento para tener sistemas de gestión automáticos alrededor de procesos que sean lineales o máquinas de estados finitas (django-flow), y al sitio de gestión de socies de la Asociación Civil.

También trabajé con fades (principalmente ayudando/mentoreando a otres que laburaron en el proyecto), participé en una charla que nos dio Fisa sobre pytest (¡me encantó!), y además llevé adelante un juego grupal que es una especie de coding dojo rotativo rápido, algo que seguramente repetiremos en el futuro porque estuvo buenísimo.

El primer día nos levantamos tempranito, no había gente "externa" todavía

Obvio también tuvimos los clásicos juegos de mesa, luego de cenar (menos la primer noche, que nos recontracolgamos con José Luis, Sofi, Matu y Leandro laburando hasta las cuatro de la mañana en ese experimento que mencionaba antes, cuando ya no entendíamos ni lo que pensábamos.

Juegos de mesa, decía. Volví a disfrutar el Galaxy Trucker (me encanta, debería conseguirlo), perdí un par de veces en el Resistance, y estuve rondando/ayudando bastante en una partida de Munchkin.

Jugando al Galaxy Trucker

También conocí juegos nuevos: uno 100% de interacción humana, el "Psicólogo" (me gustó, es un toque bizarro, al estilo Mao, no es para cualquier grupo), y dos de mesa: el Aquarius que no me entusiasmó mucho, y el Bohnanza, que me pareció bárbaro.

Y claro que hubo actividades "externas". No sólo nos metimos varias veces a la pileta, sino que hicimos malabares, y además una de las noches fuimos al observatorio que está en el predio donde además de recibir una charla informativa que me gustó bastante, pudimos tener la experiencia de observar un grupito de estrellas alrededor de Hatysa a través de un telescopio que en general sólo está disponible para estudios científicos, fue genial.

Trabajando, pensando, mirá que gente concentrada

En fin. No, no voy a repetir que es el mejor evento del año. Pero sí que sí voy a afirmar que el PyCamp es el "vine por el lenguaje, me quedé por la comunidad" en su máxima expresión.

Todas las fotos (mías y algunas choreadas del grupo de Telegram que tuvimos), acá, y les dejo también estas que compartieron muches otros asistentes al evento.

Facundo Batista: Empezando el año a todo trapo

Menos mal que salió descanso unos días a fin de Diciembre, porque el año empezó con ritmo bastante movidito.

Por un lado, la actividad de les peques, que hicieron "colonia" a la tarde, con lo cual los tenía a la mañana en casa, les preparaba el almuerzo, los llevaba al club, y muchas veces también los iba a buscar.

Aprovechaba ese par de horas bien temprano en la mañana, más la tarde solo, para meterle duro y parejo al laburo. Después hacía malabares. Pero ellos lo disfrutaban un montón, así que valía la pena.

Por otro lado, a Febrero lo tuve partido en dos porque viajé por laburo. A Malta, destino al que ya había ido en 2014, pero que ahora revisité en un contexto muy cambiado, ya que a nivel trabajo estoy en algo muy diferente, y a nivel equipo es casi todo distinto... es más, considerando un par de faltazos por razones de fuerza mayor, tendría que ver realmente si hubo alguien que se repitiera en el equipo de aquella vez y la de esta. Los tiempos cambian.

Callecita interna de Valleta, la capital de Malta

Más allá de eso, aproveché a pasear por lugares que no había conocido :). Todas las fotos acá.

Encima, esta semanita, que acabo de volver, la tengo saturada de reuniones (en la escuela de les peques y un par más), y el viernes muy temprano ya parto para San Rafael donde hacemos el PyCamp de este año.

El mismo termina el martes, y yo a diferencia de otras veces donde me quedo bien hasta el final, viajo el mismo martes a la tarde de regreso porque el miércoles arrancan las clases, y quiero estar ahí.

El hotel tenía muy buena vista

A nivel agenda lo bueno es que ya los horarios se normalizan a partir de esa primer semana de Marzo, o casi: más allá de un par de semanas de horarios especiales para Male para adaptarse al doble turno, después ambos peques pasan a tener el mismo horario y eso me simplifica bastante la operatoria diaria.

Lo cual es esencial para poder encarar el resto del año e ir haciendo todo lo que tengo ganas de hacer :)

Marcos Dione: remotely-upgrading-centos6-centos7

At $WORK we're in the middle of a massive OS upgrade in all of our client's appliances. Unfortunately, the OS is CentOS, which has no official method to do it except for 'reinstall'. This of course is unacceptable for us, we need an automatic system that can do it remotely. We have developed such a thing, mostly based on RedHat's official tool, redhat-upgrade-tool (rut). There are plenty of tutorials on how to do it on the web.

All was fine until we hit clients using UEFI instead of BIOS[1]. Unluckily rut does not seem to handle the grub to grub2 transition wery well in this case. I had to create a dummy RPM package that, if it detects that the machine boots via EFI, generates a grub2 config file in /boot/efi/EFI/centos/grub.cfg and runs efibootmgr to tell the EFI system to use the EFI partition and file to boot. Here's the whole %posttrans section:

(
    set -x

    efi_parition=$(mount | awk '$3 == "/boot/efi" { print $1 }' | cut -d / -f 3)

    if [ -n "${efi_parition}" ]; then
        # create grub2 config file for the EFI module
        mkdir -pv /boot/efi/EFI/centos
        grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

        # it's a shame we have to this here and then efibootmgr it's going to do the inverse
        # convert /boot/efi -> /dev/fooX -> foo + X
        efi_disk=$(find -H /sys/block/* -name ${efi_parition} | cut -d / -f 4)
        # we can't just cut the last char (part number could be beyond 9)
        # and we can't assume it's just after the disk name (disk: nvme0n1, partition: nvme0n1p1, part_num: 1)
        efi_parition_number=$(echo ${efi_parition} | egrep -o '[0-9]+$')

        # create an entry in the EFI boot manager and make it the only one to boot
        efibootmgr --create --bootnum 0123 --label grub2 --disk "/dev/${efi_disk}" \
            --part ${efi_parition_number} --loader \\EFI\\centos\\grubx64.efi --bootorder 0123
    fi
) &> /tmp/upgrade-centos-6.9-7.2.log

Another part of the automatic upgrade script detects EFI, installs grub2-efi and that other RPM, so the code is executed during the actual process of upgrading to CentOS7.

But the gist of this post is about how did I manage to test such a thing. Luckily we had 3 Lenovo servers laying around, but I could only reach them via ssh and IPMI. If you don't know, BMC/IPMI is a 'second' computer in your computer with which, among many, many other things, you can remotely turn on/off your computer, access the console remotely and even upload an ISO image and mount it in the 'main' machine as if it was a USB CD-ROM disk. This last one will come handy later. Notice that this is the system in the middle of Bloomberg's article about China infiltrating into companies, so you better get acquainted to it.

These Lenovo machines already had CentOS7 installed, so the first step was to install CentOS6. This should be possible by copying a CentOS6's installer and booting it via grub2. But before that I also had to enable UEFI boot. This broke booting, because the existing system had grbu2 installed in a disk's MBR instead on a EFI partition, and the disk had DOS partitions instead of GPT ones.

So the steps I took to fix this were:

On The client machine:

  • Install the icedtea-plugin (the remote console is a java applet).
  • Download Super Grub2 Disk.
  • Connect via IPMI to the server, request the remote console.

On the remote server (all this through the IMPI remote console):

  • Login, download CentOS6 installer iso.
  • Tell the virtual device manager to add an ISO, using the SG2D ISO.
  • Reboot the machine.
  • Get into the Setup (another EFI module), enable EFI boot mode, reboot.
  • Start the Boot Selection Menu, boot from the virtual CD.

On SG2D/Grub2's interface:

  • Enable GRUB2's RAID and LVM support (the CentOS6 install ISO was in a LVM part).
  • Selected Print the devices/partitions to find my LVM part.
  • Loosely followed these instructions to boot from the ISO image:
    • Open the Grub2 prompt (Press c).
    • loopback loop (lvm/vg0-root)/root/CentOS-6.9-x86_64-minimal.iso
    • linux (loop)/images/pxeboot/vmlinuz
    • linux (loop)/images/pxeboot/initrd.img
    • boot

This boots the installer, but it's not able to find the source media, so install from a URL. After setting up TCP/IP (fixed in my case), I used the URL http://mirrors.sonic.net/centos/6/os/x86_64/.

Freebie: More than I ever wanted to know about UEFI.


[1] In UEFI systems, the BIOS/Legacy mode is just an EFI module that provides the legacy boot method; that is, boots from a disk's MBR.


sysadmin centos uefi grub2

Facundo Batista: Reponiendo la patente

El año pasado nos fuimos unos días con la familia a Ostende, entre Navidad y Año Nuevo. En general tuvimos buen tiempo (aunque mucho viento, no pudimos hacer mar), pero hubo un par de lluvias complicadas.

Uno de los chaparrones, de especialmente alta intensidad en poco tiempo, nos encontró pegando una vuelta por Valeria del Mar. Cuando empezaron a caer las gotas (cada una como un baldazo de agua) apuntamos para volver al hotel.

En la vuelta pasamos por tres zonas inundadas. Las primeras dos era sólo cuestión de tomársela con calma. Pero la tercera y última estaba tan complicada que dudé mucho en pasar: para el caso aunque camionetas grandes pasaban bien, muchos autos pegaban la vuelta.

Pero le calculé bien y me mandé. Pasamos, aunque el agua llegó al parabrisas al bajarse la trompa del auto en un badén en la mitad de lo inundado. Llegamos al hotel, y cuando me bajé del auto, me di cuenta que habíamos perdido la patente de adelante!

Hice algunas averiguaciones, y no había mucha opción: hay que hacer un duplicado de las chapas y ponérselas al auto (lo cual tarda unas semanas, pero mientras tanto se lleva pegado al vidrio un papelito de trámite provisorio). Pero para eso hay que ir al Registro Automotor, y nosotros habíamos perdido la patente un sábado al mediodía, y el domingo volvíamos a casa, así que no había chance.

No todos los días llovió, eh, hicimos playa también

Fui a la policía, para hacer una declaración de que había perdido la patente. Luego de renegar un poco me la hicieron, pero la verdad es que a la hora de circular esa declaración no me servía para nada: no se puede circular sin patente. Pero tampoco iba a mandar el auto en grúa y a la familia en colectivo hasta casa. Así que me arriesgué, puse adelante la patente de atrás, y volvimos a casa.

Por suerte la caminera nunca me paró, y un par de días después ya hice el trámite en el registro número 3 de Olivos (un placer hacer trámites ahí, siempre te ofrecen más soluciones que trabas o inconvenientes).

A los quince días llamé para ver si estaban las chapas, y no. Tres días después volví a intentar, tampoco. Al otro día me llamaron del registro y me dijeron que les habían avisado que las chapas están demoradas, así que antes de que se venza el mes tenía que pasar a que me "renueven" el papelito provisorio que te permite circular mientras tanto.

El jueves pasado finalmente me llamaron para avisarme que las placas estaban listas, así que al otro día las fui a buscar (y obvio las puse al toque, porque como uno entrega los papelitos provisorios, sino no se puede circular).

Pero al ponerlas me di cuenta que los tornillos de adelante no agarraban bien. Les puse unos más gruesos que tenía por ahí, pero igual no quedaron bien, ¡es que las patentes en la Renault Stepway van agarradas al paragolpes, y el mismo es de plástico! Mejor prevenir que curar, pensé, entonces le puse poxipol a los agujeritos y puse los tornillos, para que queden mejor agarrados.

Pero soy porfiado, y quiero evitar tener el mismo quilombo la próxima vez que cruce un charco (o evitar en lo posible que me las roben, que también es una posibilidad), así que le puse algunos remaches (dos atrás, abajo, para que la chapa no se levante, y cuatro adelante, dos abajo y dos arriba).

La próxima que pierda la patente será porque perdí el paragolpes :p

Le puse varios remaches, no se sale más

Marcos Dione: customizing-the-python-language

Programming languages can be viewed as three things: their syntax and data model, their standard library and the third party libraries you can use. All these define the expressiveness of the language, and determine what can you write (which problems you can solve) and how easily or not. This post/talk is about how expressive I think Python is, and how easy it is or not to change it.

I said that we solve problems by writing (programs), but in fact, Python can solve several problems without really writing a program. You can use the interpreter as a calculator, or use some of the modules a programs:

$ python3 -m http.server 8000

With that you can serve the current directory via HTTP. Or do this:

$ python3 -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 3: 30.2 usec per loop
$ python3 -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 3: 27.5 usec per loop
$ python3 -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 3: 23.2 usec per loop

to check which method is faster. Notice that these are modules in the standard library, so you get this functionality out of the box. Of course, you could also install some third party module that has this kind of capability. I find this way of using modules as programs very useful, and I would like to encourage module writers to consider providing such interfaces with your modules if you think it makes sense.

Similarly, there are even programs written in Python that can also be used as modules, which I think should also be considered by all program writers. For instance, I would really like that ssh was also a library; of course, we have paramiko, but I think it's a waste of precious developer time to reimplement the wheel.

The next approach I want to show is glue code. The idea is that you take modules, functions and classes, use them as building blocks, and write a few lines of code that combine them to provide something that didn't exist before:

import centerlines, psycopg2, json, sys, shapely.geometry, shapely.wkt, shapely.wkb

tolerance = 0.00001

s = sys.stdin.read()
data = json.loads(s)
conn = psycopg2.connect(dbname='gis')

ans = dict(type='FeatureCollection', features=[])

for feature in data['features']:
    shape = shapely.geometry.shape(feature['geometry'])

    shape = shape.simplify(tolerance, False)
    skel, medials = centerlines.skeleton_medials_from_postgis(conn, shape)
    medials = centerlines.extend_medials(shape, skel, medials)
    medials = shapely.geometry.MultiLineString([ medial.simplify(tolerance, False)
                                                 for medial in medials ])

    ans['features'].append(dict(type='Feature',
                                geometry=shapely.geometry.mapping(medials)))

s = json.dumps(ans)
print(s)

This example does something quite complex: it takes a JSON representation of a polygon from stdin, calculates the centerline of that polygon, convert is back to a JSON representation and outputs that to stdout. You could say that I'm cheating; most of the complexity is hidden in the shapely and centerlines modules, and I'm using PostgreSQL to do the actual calculation, but this is what we developers do, right?

Once the building blocks are not enough, it's time to write our own. We can write new functions or classes that solve or model part of the problem and we keep adding glue until we're finished. In fact, in the previous example, centerlines.skeleton_medials_from_postgis() and centerlines.extend_medials() are functions that were written for solving this problem in particular.

But the expressiveness of the language does not stop at function or method call and parameter passing; there are also operators and other protocols. For instance, instead of the pure OO call 2.add(3), we can simply write 2 + 3, which makes a lot of sense given our background from 1st grade. Another example which I love is this:

file = open(...)
line = file.readline()
while line:
    # [...]
    line = file.readline()
file.close()

versus

file = open(...)
for line in file:
    # [...]
file.close()

The second version is not only shorter, it's less error prone, as we can easily forget to do the second line = file.readline() and iterate forever on the same line. All this is possible thanks to Python's special methods, which is a section of the Python reference that I definitely recommend reading. This technique allowed me to implement things like this:

command1(args) | command2(args)

which makes a lot of sense if you have a shell scripting background; or this:

with cd(path):
    # this is executed in path

# this is executed back on the original directory

which also will ring a bell for those of you who are used to bash (but for those of you who don't, it's written as ( cd path; ... )). I can now even write this:

with remote(hostname):
    # this body excecutes remotely in hostname via ssh

Following this same pattern with the file example above, we can even simplify it further like so:

with open(...) as file:
    for line in file:
        # [...]

This has the advantage that not only relieves us from closing the file, that would happen even if an unhandled exception is raised within the with block.

Special methods is one of my favorite features of Python. One could argue that this is the ultimate language customization, that not much more can be done. But I'm here to tell you that there is more, that you can still go further. But first let me tell you that I lied to you: the pipe and remote() examples I just gave you are not (only) implemented with special methods. In fact, I'm using a more extreme resource: AST meddling.

As any other programming language, Python execution goes through the steps of a compiler: tokenizing, parsing, proper compilation and execution. Luckily Python gives us access to the intermediate representation between the parsing and compilation steps, know as Abstract Syntax Tree, using the ast.parse() function. Then we can modify this tree at our will and use other functions and classes in the ast module to make sure this modifications are still a valid AST, and finally use compile() and exec() to execute the modified tree.

For instance, this is how I implemented |:

class CrazyASTTransformer(ast.NodeTransformer):
    def visit_BinOp(self, node):
        if type (node.op) == BitOr:
            # BinOp( left=Call1(...), op=BitOr(), right=Call2(...) )
            update_keyword(node.left,
                           keyword(arg='_out', value=Name(id='Pipe', ctx=Load())))
            update_keyword(node.left,
                           keyword (arg='_bg', value=Name(id='True', ctx=Load())))
            ast.fix_missing_locations(node.left)
            update_keyword(node.right, keyword(arg='_in', value=node.left))
            node = node.right
            # Call2(_in=Call1(...), _out=Pipe, _bg=True)

        return node

I used Call1 and Call2 to show which is which; they're really ast.Call objects, which represent a function call. Of course, once I rewrote the tree, most of the code for how the commands are called and how the pipe is set up is in the class that implements commands, which is quite more complex.

For remote() I did something even more extreme: I took the AST of the body of the context manager, I pickle()'d it, added it as an extra parameter to remote(), and replaced it with pass as the body of the context manager, so the AST becomes the equivalent of:

with remote(hostname, ast_of_body_pickled):
    pass

When the context manager really executes, I send the AST over the ssh connection together with the locals() and globals() (its execution context), unpickle in the other side, restore the context, continue with the compile()/exec() dance, and finally repickle the context and send it back. This way the body can see its scope, and its modifications to it are seen in the original machine.

And that should be it. We reached the final frontier of language customization, while maintaining compatibility, through the AST, with the original interpreter...

Or did we? What else could we do? We certainly can't[1] modify the compiler or the execution Virtual Machine, and we already modify the AST, can we do something with Python's tokenizer or parser? Well, like the compiler and the VM, they're written in C, and modifying them would force us to fork the interpreter, with all the drawbacks of maintaining it. But can we make another parser?

On one hand, the Python standard library provides a couple of modules to implement your own parsers: tokenize and parser. If we're inventing a new language, this is one way to go, but if we just want a few minor changes to the original Python language, we must implement the whole tokenizer/parser pair. Do we have other options?

There is another, but not a simple one. pypy is, among other things, a Python implementation written entirely in (r)Python. This implementation runs under Python legacy (2.x), but it can parse and run current Python (3.x) syntax[4]. This implementation includes the tokenizer, the parser, its own AST implementation[2], and, of course, a compiler and the VM. This is all free software, so we can[3] take the tokenizer/parser combination, modify it at will, and as long as we produce a valid (c)Python AST, we can still execute it in the cPython compiler/VM combination.

There are three main reasons to modify this code. First, to make it produce a valid cPython AST, we will need to modify it a lot; cPython's compile() function accepts only ASTs built with instances of the classes from the ast module (or str or bytes[5]), it does not indulge into duck-typing. pypy produces ASTs with instances of its own implementation of the ast module; rewriting the code is tiresome but not difficult.

Second, on the receiving side, if we're trying to parse and execute a particular version of Python, we must run it at least under the oldest Python version that handles that syntax. For instance, when I wanted to support f-strings in my language, I had no option but to run the language on top of Python-3.6, because that's when they were introduced. This meant that a big part of the modifications we have to do is to convert it to Py3.

Finally, we must modify it so it accepts the syntax we want; otherwise, why bother? :)

So what do we get with all this fooling around? Now we can modify the syntax so, for instance, we can accept expressions as keyword argument names, or remove the restriction that keyword and positional arguments must be in a particular order:

grep(--quiet=True, 'mdione', '/etc/passwd')

After we modify the parser, it's able to generate an AST, but this AST is invalid because the compiler will reject it. So we still have to recourse to more AST meddling before passing it to the compiler. What I did for the parameter meddling was to create a o() function which accepts a key and a value, so --quiet=True becomes the AST equivalent of o('--quiet', True). Once we've finished this meddling, the original, official, unmodified interpreter will happily execute our monster.

All of these techniques are used in ayrton in some way or another, even the first one: I use python3 -m unittest discover ayrton to run the unit tests!


[1] Well, technically we can, it's free software, remember!

[2] The cPython AST, while being part of the standard library, is not guaranteed to be stable from versions to version, so we can't really consider it as part of the API. I think this is the reason why other implementations took the liberty to do it their own way.

[3] ... as long as we respect the license.

[4] In fact some of the work is implemented in the py3.5 branch, not yet merged into default. I'm using the code from this branch.

[5] This would also be another avenue: feed compile() the definite bytecode, but that looks like doing a lot of effort, way more than what I explain here.


python ayrton

Mariano Guerra: Download frontend generated data to a file with clojurescript

I had to write this because I couldn't find it with a quick search, so here it his for future people like me (or me).

This is how you allow users to download a file from data generated in the browser with the save file dialog in cljs:

(defn to-json [v] (.stringify js/JSON v))

(defn download-object-as-json [value export-name]
        (let [data-blob (js/Blob. #js [(to-json value)] #js {:type "application/json"})
                  link (.createElement js/document "a")]
          (set! (.-href link) (.createObjectURL js/URL data-blob))
          (.setAttribute link "download" export-name)
          (.appendChild (.-body js/document) link)
          (.click link)
          (.removeChild (.-body js/document) link)))

You call it like this:

(download-object-as-json (clj->js {:hello "world"}) "myfile.json")

As an extra, it shows many idioms to interoperate with js and js objects.

Marcos Dione: third-party-apps-not-working-in-fairphone-os-18.09.2

More than a year ago I bought a FairPhone2 because that's what a geek with social responsible inclinations (and some hardware hacking) does. It came with Android Marshmallow (aka 6), but last December I bit the bullet and upgraded to Nougat (7.1). Also, as any megacorporation paranoid geek would do, I don't have a Google account (even when 90%+ of my mails ends up in their humongous belly, but who uses mail nowadays anyways...), so I have been using it with F-Droid and Yalp Store.

The upgrade went smoothly, and almost right after it I was poking aroung the Yalp Store when I saw several system updates, including the Android System WebView. This component is the one responsible of showing web content in your apps, and, believe me, you use it more than you think. The new Android came with version 67.0.3396.87, and Yalp Store was offering v71.0.3578.99, so I didn't think about it and installed the further upgrade, along with most of the apps that I knew were not installed through F-Droid[1]. There's also the fact that since Nougat, ASWB is deprecated in favor of embracing Chrome, but I have it disabled in my phone, just like most of the Google Apps (including Google Play Services).

The issue came when I tried to launch the official Selfoss reader. The list of articles worked fine, but trying to read one made the app crash. Even worse were the two homebanking apps I have: they didn't even show their main screen.

Thanks to a small troubleshooting session with jochensp in the #fairphone ICR channel, we found out that in fact it was a ASWB problem (hint: use adb logcat). Once more I had to use one of those don't-know-how-shady-it-is APK mirror sites (I used APKMirror, if you're curious, but don't blame me if the soft you install from there comes with all kinds of troyans).

The first thing I tried was to downgrade to the original version, so I downloaded the closest one I found (they didn't have the exact version, which makes me wonder how ofthen do they scan apps for upgrades), but downgrades don't work, even with adb install -r -d. For some reason, the same site offered a newer version than Yalp Store (72.0.3626.53, which I just found out it's a beta version!), so I upgraded (manual download + install) and that fixed it!


[1] There's an issue where Yalp Store tries to manage the apps installed via F-Droid by offering the versions available in Google Play, but most if the time it doesn't work because F-Droid recompiles everything and I think the keys are different. I hadn't compared if the versions offered by YS are really newer than those in FD).


android fairphone

Damián Avila: Abandoning the oquanta domain name

This is a very short but important post!

As you probably know, this blog can be found as a subdomain of oquanta.info.

Starting Jan 23rd, 2019, I will be abandoning that domain name because GoDaddy is asking me unreasonable prices for renewal... and, actually, I am not using that domain except for the blog itself.

As you probably know as well, for several years, this blog has been hosted in gh-pages, so I will just use the default and expected URL provided by Github: http://damianavila.github.io/blog

Please, make sure to bookmark/save/link the correct URL if you want to keep reading about some of my stuff ;-)

Have a great start of the week!

Damián Avila: Abandoning the oquanta domain name

This is a very short but important post!

As you probably know, this blog can be found as a subdomain of oquanta.info.

Starting Jan 23rd, 2019, I will be abandoning that domain name because GoDaddy is asking me unreasonable prices for renewal... and, actually, I am not using that domain except for the blog itself.

As you probably know as well, for several years, this blog has been hosted in gh-pages, so I will just use the default and expected URL provided by Github: http://damianavila.github.io/blog

Please, make sure to bookmark/save/link the correct URL if you want to keep reading about some of my stuff ;-)

Have a great start of the week!