Facundo Batista: Metaclasses in Python, the easy way (a real life example)


They say that metaclasses make your head explode. They also say that if you're not absolutely sure what are metaclasses, then you don't need them.

And there you go, happily coding through life, jumping and singing in the meadow, until suddenly you get into a dark forest and find the most feared enemy: you realize that some magic needs to be done.

The necessity

Why you may need metaclasses? Let's see this specific case, my particular (real life) experience.

It happened that at work I have a script that verifies the remote scopes service for the Ubuntu Phone, checking that all is nice and crispy.

The test itself is simple, I won't put it here because it's not the point, but it's isolated in a method named _check, that receives the scope name and returns True if all is fine.

So, the first script version did (removed comments and docstrings, for brevity):

    class SuperTestCase(unittest.TestCase):

        def test_all_scopes(self):
            for scope in self._all_scopes:
                resp = self._check(scope)

The problem with this approach is that all the checks are inside the same test. If one check fails, the rest is not executed (because the test is interrupted there, and fails).

Here I found something very interesting, the (new in Python 3) subTest call:

    class SuperTestCase(unittest.TestCase):

        def test_all_scopes(self):
            for scope in self._all_scopes:
                with self.subTest(scope=scope):
                    resp = self._check(scope)

Now, each "sub test" internally is executed independently of the other. So, they all are executed (all checks are done) no matter if one or more fail.

Awesome, right? Well, no.

Why not? Because even if internally everything is handled as independent subtest, from the outside point of view it still is one single test.

This has several consequences. One of those is that the all-inside test takes too long, and you can't know what was going on (note that each of these checks hit the network!), as the test runner just show progress per test (not subtest).

The other inconvenient is that there is not a way to call the script to run only one of those subtests... I can tell it to execute only the all-inside test, but that would mean to execute all the subtests... which, again, takes a lot of time.

So, what I really needed? Something that allows me to express the assertion in one test, but that in reality it were several methods. So, I needed something that, from a single method, reproduce them so the class actually had several ones. This is, write code for a class that Python would find different. This is, metaclasses.

Metaclasses, but easy

Luckily, since a couple of years ago (or more), Python provides a simpler way to achieve the same that could be done with metaclasses. This is: class decorators.

Class decorators, very similar to method decorators, receive the class that is defined below itself, and its response is considered by Python the real definition of the class. If you don't have the concept, you may read a little here about decorators, and a more deep article about decorators and metaclasses here, but it's not mandatory.

So, I wrote the following class decorator (explained below):

    def test_multiplier(klass):
        """Multiply those multipliable tests."""
        for meth_name in (x for x in dir(klass) if x.startswith("test_")):
            meth = getattr(klass, meth_name)
            argspec = inspect.getfullargspec(meth)

            # only get those methods that are to be multiplied
            if len(argspec.args) == 2 and len(argspec.defaults) == 1:
                param_name = argspec.args[1]
                mult_values = argspec.defaults[0]

                # "move" the usefult method to something not automatically executable
                delattr(klass, meth_name)
                new_meth_name = "_multiplied_" + meth_name
                assert not hasattr(klass, new_meth_name)
                setattr(klass, new_meth_name, meth)
                new_meth = getattr(klass, new_meth_name)

                # for each of the given values, create a new method which will call the given method
                # with only a value at the time
                for multv in mult_values:
                    def f(self, multv=multv):
                        return new_meth(self, **{param_name: multv})

                    meth_mult_name = meth_name + "_" + multv.replace(" ", "_")[:30]
                    assert not hasattr(klass, meth_mult_name)
                    setattr(klass, meth_mult_name, f)

        return klass

The basics are: it receives a class, it returns a slightly modified class ;). For each of the methods that starts with "test_", I checked those that had two args (not only 'self'), and that the second argument were named.

So, it would actually get the method defined in the following structure and leave the rest alone:

    class SuperTestCase(unittest.TestCase):

        def test_all_scopes(self, scope=_all_scopes):
            resp = self.checker.hit_search(scope, '')

For that kind of method, the decorator will move it to something not named "test_*" (so we can call it but it won't be called by automatic test infrastructure), and then create, for each value in the "_scopes" there, a method (with a particular name which doesn't really matter, but needs to be different and is nice to be informative to the user) that calls the original method, passing "scope" with the particular value.

So, for example, let's say that _all_scopes is ['foo', 'bar']. Then, the decorator will rename test_all_scopes to _multiplied_test_all_scopes, and then create two new methods like this::

    def test_all_scopes_foo(self, multv='foo'):
        return self._multiplied_test_all_scopes(scope=multv)

    def test_all_scopes_foo(self, multv='bar'):
        return self._multiplied_test_all_scopes(scope=multv)

The final effect is that the test infrastructure (internally and externally) finds those two methods (not the original one), and calls them. Each one individually, informing progress individually, the user being able to execute them individually, etc.

So, at the end, all gain, no loss, and a fun little piece of Python code :)

Facundo Batista: Novedades pythónicas: fades, CDPedia, Django y curso


Algunas, varias y sueltas.

A nivel de proyectos, le estuvimos metiendo bastante con Nico a fades. La verdad es que la versión 2 que sacamos la semana pasada está piolísima... si usás virtualenvs, no dejes de pegarle una mirada.

Otro proyecto con el que estuve es CDPedia... la parte de internacionalización está bastante potable, y eso también me llevó a renovar la página principal que te muestra cuando la abrís, así que puse a tirar una nueva versión de la de español, y luego seguirá una de portugués (¡cada imagen tarda como una semana!).

Hace un rato subí a la página de tutoriales de Python Argentina el Tutorial de Django en español (¡gracias Matías Bordese por el material!). Este tuto antes estaba en un dominio que ahora venció, y nos pareció interesante que esté todo en el mismo lugar, facilita que la gente lo encuentre.

Finalmente, empecé a organizar mi Segundo Curso Abierto de Python. Esta vez lo quiero hacer por la zona de Palermo, o alrededores (la vez pasada fue en microcentro). Todavía no tengo reservado un lugar, y menos fechas establecidas, pero el formato va a ser similar al anterior. Con respecto al sitio, si alguien conoce un buen lugar para alquilar "aulas", me avisa, :)

Hernán Grecco: PyVISA-sim. Test your PyVISA applications without connected instruments

I have just released PyVISA-sim 0.1. PyVISA-sim is a backend for PyVISA. It allows you to simulate devices and therefore test your applications without having real instruments connected.

While there still a lot of features to add, this early release already allows you to:
- play around with a simulated device.
- write your own simulated device in YAML files. 
- simulated devices can include commands and properties which are automatically generated from the YAML file.
- Properties can have some basic validation rules.

Install it using:

    pip install -U pyvisa-sim

Code: https://github.com/hgrecco/pyvisa-sim
Docs: http://pyvisa-sim.readthedocs.org/
Tracker: https://github.com/hgrecco/pyvisa-sim/issues

Hernán Grecco: Lantz 0.3 is out: better PyVISA support, leaner drivers, great GUI building blocks

   Publicado: Lantz is a Python automation and instrumentation toolkit that allows you to control scientific instruments in a clean and efficient manner writing pure Python code.

After waiting for a long time, Lantz 0.3 is out. It took a while, but it was for a good reason: we were toying, playing and testing with new ideas to make Lantz better. I am going to go quickly over some of them.

MessageBasedDriver: a class to rule them all

MessageBasedDriver  replaces all previous Driver classes for message based instruments. It leverages the power of PyVISA to talk over many different interfaces. But remember that this does not mean that you require NI-VISA installed. You can still talk via the pyvisa-py backend which uses PySerial / PyUSB / Python Standard library. You can also use the pyvisa-sim backend to simulate devices!

Great GUI applications

Lantz provides two classes to help you build applications: Backend and Frontend. The first contains the logic of your application and the second the GUI. This keeps things easier to test and develop. It also allows you to call you application without a GUI (for example from the command line) or with a different GUI (for example a debug GUI with more information).

See for example here

App Building Blocks

Common structures such as looping or scanning are provided as blocks. These are combinations of Backend and Frontend that can be composed within your application. This enables rapid development of responsive, multithreaded applications.

Some of the blocks are showcased here

I will blog about this things in future posts. But in the meantime, you should really look at the docs.

Project documentation: ReadTheDocs

Public source code repository: GitHub

But the most important piece of news is not technical but social. Triggered by his contribution to Pint, MatthieuDartiailh and I have decided to work together. He has done some awesome instrumentation related coding in eapii and HQCMeas. We will work together to put the best of them into Lantz. Thanks to this collaboration, I have no doubt that Lantz 0.4 will be even better than 0.3

I have always felt that dividing the community among many projects is a waste of time and energy. That is why this is more than just than a technical merge. This is the birth of a python instrumentation initiative that we have called LabPy. We have created an organization in github to host Lantz and other projects with a common goal: making instrumentation better in Python. Join us!

Marcos Dione: restoring-signals-before-os.exec


A short note. As both the signal module doc and the subprocess module doc (almost at the end, when it finally goes around to talk about the restore_signals parameter), Python3 disables the following signals, so it can properly report them as exceptions:


But if you're executing processes with any of the os.exec*() functions, you have to restore them by hand. Of course, the option is to use subprocess, which is best for most cases; not for ayrton.

I spent a long time analyzing the process-which-didn't-die-on-SIGPIPE's source code and wondering what was all this about. The I compared the same pipeline under bash and ayrton with strace to find the real culprit.

python ayrton

Marcos Dione: ayrton-0.4.2


Today I released another minor version of ayrton, one year after the last release, almost to the minute: Same day of the same month, same hour. Hence, I named it The 'one year is nothing' release.

The changelog, at least, is quite extensive:

  • _bg allows running a command in the background.
  • _fails allows a Command to fail even when option('-e') is on.
  • Try program_name as program-name if the first failed the path lookup.
  • Convert all arguments to commands to str().
  • chdir() is an alias of cd().
  • Capture is a class, not an arbitrary value.
  • Updated doc.
  • Fixed globals and local passed to the execution of the script.
  • Fixed some fd leakage.
  • Fixed redirection when _out and _err where Capture.
  • Fixed keyword handling while doing our black magic.
  • More, better unit tests!

Get it on github or pypi!

python ayrton

Mariano Reingart: Entrevista para PyDev of the Week

Aprovechando el cierre del año, y a modo de resumen, a continuación transcribo una entrevista (traducida) que me realizó Mike Driscoll para su blog "Mouse vs Python":


"Desarrollador Python" de la Semana: Mariano Reingart

¿Puedes contarme un poco sobre ti (hobbies, educación, etc)?:

Soy programador independiente y docente, casado con dos hijos pequeños.

Comencé a programar cuando era un chico y mi padre compró una ZX Spectrum TK-90 a fines de los '80.
Para 1989, con sólo 11 años, tomé mi primer curso "formal" de programación "Basic I" usando computadoras MSX y "D.O.S." hacia 1991.
Luego de terminar la primaria, en 1992 me anoté en un colegio con orientación de "Bachiller especializado en Informática"  (nivel secundario) donde se enseñaba Visual Basic y Visual Fox Pro en los cursos finales.

Luego de egresar, trabajé en esa escuela como Ayudante de Laboratorio, instalando mi primer servidor Slackware Linux circa 1997 para la conexión de Internet, y programando los sistemas del colegio (usando inicialmente VB + Access).
Mi primer experiencia "open source" fue desarrollando un modulo para el kernel de Linux y así usar la linea telefónica dedicada de 128Kps (incluso tuvimos que importar la placa de comunicaciones); y un controlador de kernel de ucLinux para PiCoTux (una micro-computadora embebida corriendo Linux).
También, di mis primeros pasos en la migración de bases de datos a PostgreSQL y trabajando con los primeros dispositivos "WiFi".

Luego, estudié Ingeniería Electrónica por varios años, pero me dí cuenta que mis habilidades iban más por el lado del software, por lo que me cambié de carrera a una Universidad local para Estudiar Licenciatura en Sistemas.
Finalmente me gradué en 2011 luego de varios años "sabáticos" donde gané experiencia laboral desarrollando pequeños sistemas de gestión administrativa/contable para PyMEs, y una aplicación 911(en colaboración ad-honorem con un amigo de la radio-estación policial local).

Actualmente estoy terminado la Maestría en Software Libre (Universidad Abierta de Cataluña), y el Profesorado en Disciplinas Industriales / Docencia Superior (UTN - INSPT).

¿Por qué comenzaste a utilizar Python?

Empecé a usar python en los '00, buscando alternativas a Visual Basic.
Lo elegí para mi tesis de grado en 2006 "Desarrollo Rápido de Aplicaciones bajo el Proceso de Software Personal"

Desde 2009 enseño Python en un Instituto Terciario. Comenzó en un curso pero luego acordamos (con otros profesores) implementar un Proyecto Curricular Institucional orientado al software libre, principalmente en las siguientes materias:

Basado en ello, con otro colega estamos preparando una "Diplomatura en Software Libre" (programa de cursos de formación profesional de 1 de año de duración con actividades de extensión universitaria, enfocados Python, PostgreSQL and GNU/Linux, para más información ver documento de trabajoarticulo).

Felizmente, laboralmente hoy trabajo principalmente brindando planes de soporte comercial de código abierto gracias a Python, dado que desde 2008 el proyecto PyAfipWs: "factura electrónica", iniciado desde la lista de correo PyAr, la comunidad de Python Argentina, creció, siendo usado por una base de usuarios relativamente grande (incluyendo grandes empresas, PyMEs y profesionales)

¿Que otros lenguajes de programación conoces y cual es tu favorito?

Obviamente Python es mi favorito, y tengo cierta experiencia en PHP, C/C++, Java y otros lenguajes.

Lamentablemente aún tengo que usar Visual Basic Clásico (6.0) para algunos de mis sistemas ERP "legados" (son grandes, alrededor de cientos de miles de líneas de código, y no he tenido tiempo / recursos para migrarlos a Python por el momento)

Ahora estoy investigando vb2py, un proyecto para convertir código VB a Python.
Me intriga por qué  no llegó a juntar masa crítica / tracción (el desarrollo parece estancado desde 2004), teniendo en cuenta que hay mucho código VB dando vueltas...

En el proyecto de factura electrónica, use pywin32 (de Mark Hammond et al) para hacer componentes Python usables desde lenguajes legados (VB, VFP), y bibliotecas como dbf (de Ethan Furman) para interactuar con lenguajes aún más antiguos (Clipper, xBase, etc.).
Recientemente también comencé a experimentar con Jython, para usar proyectos Python desde Java.

¿En que proyectos estás trabajando ahora?

Para mi tésis de grado, comencé a desarrollar rad2py, un entorno integrado de desarrollo (IDE) experimental y herramientas CASE de ayuda al desarrollador , previstas para ser usadas para propósitos comerciales, educativos y académicos.

Ahora estoy terminando la invesigación para mi tesis de maestría "Desarrollo Rápido de Software Libre de Alta Calidad", tratando de facilitar la vida a los desarrolladores integrando enfoques modernos como interfaces basadas en tareas (Eclipse Mylyn) y Ágil ALM (gestión del ciclo de vida de las aplicaciones), con principios disciplinados de la ingeniería de software para aseguramiento de calidad y mejora continua.

¿Que bibliotecas Python son tus favoritas (del núcleo or de terceros)?

Trato de usar solo la Biblioteca Estándar de Python + wxPython y web2py, y adicionalmente pywin32 y dbf para ciertos proyectos, como mencioné antes.

Particularmente encuentro a web2py fascinante, dado que tiene prácticamente una curva de aprendizaje plana, permite prototipado rápido, una interfaz integrada de desarrollo en linea (web), etc.
Sus enfoques no tradicionales también son interesantes para traer otras perspectivas y "pensamiento crítico", especialmente gracias al liderazgo abierto, cordial y reflexivo de Massimo Di Pierro, su creador, y por supuesto, su comunidad afable.
Descargo: soy uno de los desarrolladores "principales", no muy activo estos días debido a la falta de tiempo, pero he contribuido un depurador en linea y otras mejoras, co-escribí el libro "web2py app development cookbook" de Packt, etc.

wxPython también me merece una mención especial, el trabajo de Robin Dunn es grandioso (phoenix py3k!), los widgets agw widgets de Andrea Gavana en puro phyton son realmente impresionantes, solo para mencionar algunos desarrolladores de dicha comunidad.
También veo a wxWidgets más ortogonal y fácil de usar que otras alternativas.
Este año traté de colaborar más en profundidad con una variante experimental wxQt, como becario enmarcado en el Google Summer of Code 2014 (ahora es usable, incluso desde wxPython! y bajo Android al menos la parte C++ ...).

Cuando necesito algo no cubierto por estas opciones, tiendo a mirar hacia soluciones simples (muchas veces influenciadas por extensiones PHP), iniciando, continuando o "forkeando" ("escindiendo") otros proyectos:
  • PySimpleSoap: interfaces ad-hoc livianas para construir clientes y servidores SOAP
  • PyFPDF: clase para la generación simple de PDF basada en FPDF de  PHP y derivados
  • gui2py: un fork (bifurcación) de PythonCard (conjunto de construcción GUI visual "rápido" & simple) para evolucionarlo y modernizarlo 
Más allá de que ciertos desarrolladores los ven como esfuerzos "naif" (por decir lo más leve), la mayoría de los proyectos ha encontrado su nicho y hoy en día varias contribuciones son hechas por otros colaboradores que también los han encontrado útiles .
También ayudan a entender las tecnologías subyacentes, a proveer alternativas implementadas en "puro Python" y a experimentar con la migración a Python 3.

¿Hay algo más que quieras decir?

No, disculpas por las largas respuestas, pero la entrevista es irresistible :-) ...
También, disculpas por mi Inglés (en la entrevista original), dado que hablo español es dificultoso a veces encontrar la traducción correcta.

Gracias a vos Mike por tus esfuerzos y este proyecto sobre artículos de entrevistas, y ¡gracias a toda la comunidad Python!

Juanjo Conti: Reactivé el bot @jjsaer


Saer en Twitter

Desde hace algunos años mantengo un bot en Twitter que cita a Juan José Saer.

Hace algunas semanas, cuando la notebook que servía de servidor para esta aplicación se rompió, Saer dejó de twittear.

Hoy aproveché el día para acomodar un poco su código fuente y moverlo a un servidor de verdad.

La lista completa de las citas que va posteando está en saertweets.txt. ¡Se aceptan nuevas citas para agregar! Incluso, cualquiera puede usar este pequeño programa para crear un bot de cualquier escritor o de cualquier cosa. Si no son programadores y necesitan ayuda para hacerlo funcionar, me avisan.

Facundo Batista: Logging levels


Cuando empecé con el concepto de loguear, me parecía demasiado tener niveles. Con el tiempo y la experiencia me di cuenta que son imprescindibles, :)

En la biblioteca estándar de Python hay un módulo logging que trae varios niveles prefijados. Son estos, con una pequeña anotación de cómo los uso, más un ejemplo de la vida real (tomados de mi programa de Encuentro o de fades).

- CRITICAL: creo que nunca lo usé :)

- ERROR: problemas de todo tipo; cosas que no deberían pasar, y si pasan son un inconveniente; muchas veces el programa no continúa, o continua de forma parcial o limitada, luego de este tipo de linea logueada. En este ejemplo logueo que no se pudo bajar la lista de los backends durante una actualización (también en este caso se le avisa al usuario mediante una ventanita, y el programa sigue, aunque la actualización no se concretó):

        _, backends_file = yield utils.download(BACKENDS_URL)
    except Exception, e:
        logger.error("Problem when downloading backends: %s", e)
        tell_user("Hubo un PROBLEMA al bajar la lista de backends:", e)

- WARNING: para indicar que sucedió algo que en general no debería pasar; en general no son cosas malas, sino más bien anómalas, y no presentan una situación problemática. En el siguiente ejemplo estoy dejando registro que ignoro la opción 'quiet' que pasó el usuario (porque también pasó la opción 'verbose', que es más importante):

    if verbose and quiet:
        l.warning("Overriding 'quiet' option ('verbose' also requested)")

- INFO: información general del funcionamiento del programa, cosas que son imprescindibles saber y que siempre queremos que sean registradas; en general no involucran gran cantidad de lineas, pero permite seguir el flujo de ejecución del programa desde un nivel alto. Normalmente los programas que se entregan a los usuarios o corren en los servidores están configurados para realmente mandar a disco desde este nivel. En las siguientes dos lineas muestro lo primero que loguea Encuentro al arrancar: con qué versión de Python está siendo ejecutado y qué versión de sí mismo es:

    log.info("Running Python %s on %r", sys.version_info, sys.platform)
    log.info("Encuentro version: %r", version)

- DEBUG: toda la información necesaria para analizar en detalle la ejecución del programa. Puede involucrar grandes cantidades de información, y hasta ser un problema con respecto al uso de disco o afectar la performance, pero en general no se corren los programas en este nivel, sólo durante el desarrollo o en casos de tratar de analizar un problema específico. No es raro, por ejemplo, pedirle al usuario que ejecute el programa con un parámetro especial que configura los logs en este nivel y que trate de reproducir el problema que tuvo, para luego hacer un análisis forense de la situación. En el siguiente ejemplo estoy dejando constancia que fades tuvo que instalar pip a mano en el virtualenv:

    logger.debug("Installing PIP manually in the virtualenv")

Me ha pasado en sistemas muy complejos de necesitar un nivel más abajo que DEBUG para loguear toda aquella información que podría llegar a ser útil para un análisis del comportamiento del programa, pero que normalmente sería un exceso de datos (lo cual complica desde la lectura de los registros hasta el mismo manejo de los archivos). Entonces, usábamos un nivel TRACE, que casi nunca se prendía, para este propósito.

La macana es que el módulo de logging no tiene un nivel TRACE, pero lo creábamos a mano:

    TRACE = 5
    logging.addLevelName('TRACE', TRACE)

Fíjense el 5 ese: es que DEBUG es 10, entonces queda "más abajo". Claro, para que funcione todo, teníamos que usar un Logger custom:

    class Logger(logging.Logger):
        """Logger that support our custom levels."""

        def trace(self, msg, *args, **kwargs):
            """log at TRACE level"""
            if self.isEnabledFor(TRACE):
                self._log(TRACE, msg, args, **kwargs)

Para más información sobre la infrastructura de logging de Python y consejos generales sobre qué, cómo, o cuándo dejar registro de lo que sucede, pueden ver mi charla sobre el tema (estos sons los slides, y en algún momento se publicará acá el video de esta misma charla que dí en la PyCon de Rafaela).

Facundo Batista: PyCon 2014 en Rafaela


Acaba de pasar la sexta PyCon Argentina. Como dice el título, se hizo en Rafaela, provincia de Santa Fe.

Fuimos con Nico Demarchi en auto, salimos el miércoles a la tarde y llegamos una y monedas de la mañana, volvimos el domingo durante el día, arrancando a media mañana. Creo que es el límite de lo que haría en auto... más distancia ya iría en micro o avión.

Yo tenía que llegar el miércoles a la noche porque el jueves abría el día de talleres con Introducción a Python (modo charla extendida, ya que tenía dos horas). El jueves dí dos charlas más: Cómo debuguear en Python, y Cómo los logs me salvaron el alma.Y para cerrar (justo antes de los sorteos y foto grupal), le conté a la gente un poco cómo íbamos con el proyecto de armar la Asociación Civil de PyAr.

Mis charlas salieron bien, aunque la de debugging no me gustó del todo como la había dado (pero luego recibí buen feedback). Para el taller de Intro a Python usé por primera vez a Pysenteishon, un software muy copado y piola para ir pasando los slides desde el teléfono (¡gracias Emiliano por hacerlo!). Y para las charlas del jueves estuve por primera vez descalzo dando la presentación (era algo que quería probar desde hace rato, y aproveché que el escenario del auditorio tenía piso de madera).

Dando la charla en patas

También fuí a muchas charlas, había muchas cosas copadas para ver, y creo que me salté uno o dos timeslots nada más en toda la conferencia. Las keynotes estuvieron bien, pero no me entusiasmaron particularmente. Y todo lo fue lugar y organización estuvo genial, la verdad que se pasaron. Lo mismo con la gente con la que me (re)encontré: es un placer ser parte de una comunidad así.

Yo llevé la cámara... pero la verdad es que colgué sacando fotos. Pero la grosa de Yami sacó un montón, están todas acá. Y una de las últimas que sacó es justamente la grupal, esta que muestro acá:

Foto grupal

Y como siempre que uno no viaja durmiendo o solo, está el efecto de "PyCon extendida". Es que uno viene charlando de mil cosas, de lo más variado, pero también de proyectos, ideas, etc. Con Nico nos venía rondando en la cabeza una idea para facilitar el uso de dependencias en programas Python, estuvimos charlando con gente en la conferencia, nos dieron feedback, la idea fue mutando... y en el viaje de vuelta se nos terminó de ocurrir algo piola, que no debería ser demasiado loco de implementar; ya les traeré la novedad.

¡Pero no sólo un proyecto me traje! (como si tuviera pocos y/o mucho tiempo libre, ¿no?). Tengo ganas de hacer una "maquinita de timelapse" con una Raspi (una cajita que uno puede colgar en cualquier lado y dejarla ahí algunas horas o un par de días y arme un video de esos donde se ve todo rápido, por ejemplo este). El otro proyecto es armar una valija o caja robusta con todo lo necesario en un PyCamp (router, computadora para caché de repositorios, energía, y varios etcéteras), de manera de tener todo listo y de fácil armado, onda llegás y enchufás. Ya veremos cómo se van desarrollando ambos proyectos...