Roberto Alsina: Lois Lane, Reporting

So, 9 years ago I wrote a post about how I would love a tool that took a JSON data file, a Mako template, and generated a report using reStructured Text.

If you don't like that, pretend it says YAML, Jinja2 and Markdown. Anyway, same idea. Reports are not some crazy difficult thing, unless you have very demanding layout or need to add a ton of logic.

And hey, if you do need to add a ton of logic, you do know python, so how hard can it be to add the missing bits?

Well, not very hard. So here it is, 9 years later because I am sitting at an auditorium and the guy giving the talk is having computer problems.

Lois Lane Reports from PyPI. and GitHub

Facundo Batista: PyCon Argentina 2017


Este fin de semana se realizó una nueva PyCon en Argentina, esta vez (y es la primera vez que repetimos ciudad) en Córdoba.

Yo di dos charlas "formales"... bah, una charla ("Emulando paralelismo de forma asincrónica") y un taller ("Introducción a Python"). También di una mini-plenaria de 20 minutos donde hablé sobre la Asociación Civil Python Argentina.

Grupal

Hubieron un montón de charlas interesantes! Estas son las que más me gustaron:

  • "Distribuyendo código de py a PyPI", donde Matías Bordese nos contó todo el proceso de empaquetamiento y publicación de un programa o biblioteca en Python.
  • "Robótica educativa con software y hardware libre... y Python!", de Valentín Basel, que mostró cómo armaron el proyecto Icaro de forma que los niños puedan hacer cosas con componentes baratos o reciclado y aprender a programar en el camino.
  • "Magicicada: el fork open-source de Ubuntu One Filesync" donde Naty Bidart contó un poco la historia del servicio de sincronización de archivos en el que trabajamos en Canonical, su evolución luego de que fue liberado a Magicicada, y mostró la arquitectura del sistema y sus complejidades.
  • "Django Channeled", de Jonatas Baldin, que mostró como el concepto de "conexión permanente del cliente al servidor usado para que el servidor le pueda mandar mensajes al cliente en cualquier momento" se implementa en Django de manera elegante, integrándose correctamente con el resto del framework.
  • "Me están espiando! Cómo saber con Python si el imperialismo te persigue o te pasaste de Focusyn", de Nicolás Demarchi, donde aprendimos cómo calcular la posición de todos los satélites alrededor de la tierra, y darnos cuenta que si en un momento en particular podemos ver a determinado satélite (lo cual significa que ese satélite también nos puede ver).
  • "De la Rabbit, Pascal y Stored Procedures a la Beaglebone Black, Flask y PyZMQ", de Leandro Colombo Viña, la historia de la evolución de un hardware determinado, donde con conceptos modernos de comunicación y Python se lograron revolucionar las especificaciones del producto.
  • "Python en el browser, mil intentos y un invento", de Roberto Alsina, que nos contó un poco de historia y un poco de realidad por si queremos programar "en el browser, del lado del cliente" en Python, y no en javascript.

Las plenarias también estuvieron buenas. Aleksandra Sendecka nos contó en "Anatomy of a Code Review" el por qué y para qué de las revisiones de código por parte del equipo de trabajo, y especialmente el cómo, con un montón de consejos interesantes. Por otro lado Lucio Torre en "No hay tal cosa como un almuerzo gratis en temas de software" nos habló sobre cómo en desarrollo siempre las acciones que nos dan algo de beneficio por un lado, nos traen algo de perjuicio por otro, y que muchas veces entender dónde uno está parado (y saber leer eso, especialmente cuando uno no tiene toda la información y se basa en prejuicios) es fundamental para la evolución del desarrollo del sistema.

A nivel social, la comunidad de Python Argentina sigue mostrándose ejemplar. No sólo en lo personal (donde es una excusa para reencontrarse con amiga/os y charlar, charlar, charlar) sino también en la inclusión de nuevas personas en el grupo. El ejemplo que más me resuena a este respecto es esta serie de tuits:

Inclusión

Hasta donde sé se grabaron todas las charlas, así que avisaré cuando estén subidas. Y acá tienen fotos mías y las que sacó Yami (incluyendo la grupal que reproduzco arriba).

Damián Avila: RISE 5.1.0 is out!

We're pleased to announce the release of RISE 5.1.0!

RISE allows you show your Jupyter notebooks rendered as an executable Reveal.js-based slideshow. It is your very same notebook but presented in a slidy way!

What are the new goodies for this release?

Read more… (1 min remaining to read)

Marcos Dione: dinant-0.5

I have a love and hate relantionship with regular expressions (regexps). On one side they're a very powerful tool for text processing, but on the other side of the coin, the most well known implementation is a language whose syntax is so dense, it's hard to read beyond the most basic phrases. This clashes with my intention of trying to make programs as readable as possible[1]. It's true that you can add comments and make your regexps span several lines so you can digest them more slowly, but to me it feels like eating dried up soup by the teaspoon directly from the package without adding hot water.

So I started reading regexps aloud and writing down how I describe them in natural language. This way, [a-z]+ becomes one or more of any of the letters between lowercase a and lowercase z, but of course this is way too verbose.

Then I picked up these descriptions and tried to come up with a series of names (in the Pyhton sense) that could be combined to build the same regexps. Even 'literate' programs are not really plain English, but a more condensed version, while still readable. Otherwise you end up with Perl, and not many think that's a good idea. So, that regexp becomes one_or_more(any_of('a-z')). As you can see, some regexp language can still be recognizable, but it's the lesser part.

So, dinant was born. It's a single source file module that implements that language and some other variants (any_of(['a-z'], times=[1, ]), etc). It also implements some prebuilt regexps for common constructs, like integer, a datetime() function that accepts strptime() patterns or more complex things like IPv4 or IP_port. Conforming I start using it in (more) real world examples (or issues are filed!), the language will slowly grow.

Almost accidentally, its constructive form brought along a nice feature: you can debug() your expression so you can find out the first sub expression that fails matching:

# this is a real world example!
In [1]: import dinant as d
In [2]: line = '''36569.12ms (cpu 35251.71ms)\n'''
# can you spot the error?
In [3]: render_time_re = ( d.bol + d.capture(d.float, name='wall_time') + 'ms ' +
...:                       '(cpu' + d.capture(d.float, name='cpu_time') + 'ms)' + d.eol )

In [4]: print(render_time_re.match(line))
None

In [5]: print(render_time_re.debug(line))
# ok, this is too verbose (I hope next version will be more human readable)
# but it's clear it's the second capture
Out[5]: '^(?P<wall_time>(?:(?:\\-)?(?:(?:\\d)+)?\\.(?:\\d)+|(?:\\-)?(?:\\d)+\\.|(?:\\-)?(?:\\d)+))ms\\ \\(cpu(?P<cpu_time>(?:(?:\\-)?(?:(?:\\d)+)?\\.(?:\\d)+|(?:\\-)?(?:\\d)+\\.|(?:\\-)?(?:\\d)+))'
# the error is that the text '(cpu' needs a space at the end

Of course, the project is quite simple, so there is no regexp optimizer, which means that the resulting regexpes are less readable than the ones you would had written by hand. The idea is that, besides debugging, you will never have to see them again.

Two features are in the backburner, and both are related. One is to make debugging easier by simply returning a representation of the original expression instead of the internal regexp used. That means, in the previous example, something like:

bol + capture(float, name='wall_time') + 'ms ' + '(cpu' + capture(float, name='cpu_time')

The second is that you can tell which types the different captured groups must convert to. This way, capture(float) would not return the string representing the float, but the actual float. The same for datetime() and others.

As the time of writing the project only lives on GitHub, but it will also be available in PyPI Any Time Soon®. Go grab it!


python ayrton


[1] for someone that knows how to read English, that is.

Facundo Batista: Usando Go desde Python


¿Alguna vez necesitaron usar un código de Go desde Python? Yo sí, y acá cuento qué hice.

Antes que nada, un poco de background, para que el ejercicio no sea demasiado teórico: en el laburo tenemos que validar las licencias que se incluyen en el .snap, y aunque el formato en que están sería estándar (SPDX), una condición de contorno es usar el mismo parser/validador que se usa en snapd, para estar 107% seguros que el comportamiento va a ser el mismo hasta en los corner cases o bugs.

El detalle es que snapd está escrito en Go, y el server está escrito en Python. Entonces tengo que compilar ese código en Go y usarlo desde Python... de allí este post, claro.

Es más fácil de lo que parece, ya que el compilador de Go tiene la capacidad de buildear a "biblioteca compartida", y de ahí usarlo desde Python es casi trivial ("casi", porque tenemos que poner algo de código en C).

Para ser más claro, si queremos ejecutar "la lib de SPDX hecha en Go" desde nuestro Python, tenemos que poner dos componentes, que funcionan casi de adaptadores:

  • Un pequeño código en C para armar "como módulo" una funcioncita que recibe y entrega objetos Python, y hace la traducción al "mundo C" y llama a otra función en Go.
  • Un pequeño código en Go que traduce los parámetros desde C y llama a la biblioteca SPDX correspondiente.


Adaptador de Python a C

El archivo completo es spdx.c, paso a explicarlo haciendo antes la aclaración que es para Python 2 (que es lo que tenemos hoy en ese servicio), pero si fuera para Python 3 sería muy parecido (la idea es la misma, cambian algunos nombres, revisen acá).

Antes que nada, incluir la lib de Python

    #include <Python.h>

Vamos a llamar a una función de Go, necesitamos explicitar lo que va recibir (una cadena de bytes, que a nivel de C es un puntero a chars)  y lo que nos devuelve (un número, que interpretaremos como bool).

    long IsValid(char *);

Definimos la función que vamos a llamar desde Python... es sencilla porque es genérica, recibe self y argumentos, devuelve un objeto Python:

    static PyObject *
    is_valid(PyObject *self, PyObject *args)

El cuerpo de la función es sencillo también. Primero definimos 'source' (el string con la licencia a validar) y 'res' (el resultado), luego llamamos a PyArg_ParseTuple que nos va a parsear 'args', buscando una cadena ('s') la cual va a poner en 'source' (y si algo sale mal nos vamos enseguida, para eso está el 'if' alrededor).

    {
        char * source;
        long res;

        if (!PyArg_ParseTuple(args, "s", &source))
            return NULL;

Finalmente llamamos a IsValid (la función en Go), y a ese resultado lo convertimos en un objeto de Python tipo bool, que es lo que realmente devolvemos:

        res = IsValid(source);
        return PyBool_FromLong(res);
    }

Ahora que tenemos nuestra función útil, debemos meterla en un módulo, para lo cual tenemos que definir qué cosas van a haber en dicho módulo. Entonces, armamos la siguiente estructura, con dos lineas; la primera habla sobre nuestra función, la última es una marca en la estructura para que sepa que es el final.

    static PyMethodDef SPDXMethods[] = {
        {"is_valid", is_valid, METH_VARARGS, "Check if the given license is valid."},
        {NULL, NULL, 0, NULL}
    };

En la linea útil tenemos:

  • "is_valid": es el nombre de la función que vamos a usar desde afuera del módulo
  • is_valid: es una referencia a la función que tenemos definida arriba (para que sepa qué ejecutar cuando llamamos a "is_valid" desde afuera del módulo.
  • METH_VARARGS: la forma en que recibe los argumentos (fuertemente atado a como luego los parseamos con PyArg_ParseTuple arriba.
  • "Check ...": el docstring de la función.

Para terminar con este código, va el inicializador del módulo, con un nombre predeterminado ("init" + nombre del módulo), y la inicialización propiamente dicha, pasando el nombre del módulo y la estructura que acabamos de definir arriba:

    PyMODINIT_FUNC
    initspdx(void)
    {
        (void) Py_InitModule("spdx", SPDXMethods);
    }


Adaptador de C a Go

El archivo completo es spdxlib.go.

Tenemos que meter el código en un paquete 'main'

    package main

Importamos el código para SPDX de snapd (tienen que bajarlo antes con go get github.com/snapcore/snapd/spdx):

    import "github.com/snapcore/snapd/spdx"

Importamos adaptadores desde/a C, indicando que cuando buildeemos vamos a usarlo desde Python 2:

    // #cgo pkg-config: python2
    import "C"

La función propiamente dicha, donde indicamos que recibimos un puntero a char de C y vamos a devolver un bool:

    //export IsValid
    func IsValid(license *C.char) bool {

El cuerpo es nuevamente sencillo: llamamos al ValidateLicense de SPDX (convirtiendo primero la cadena a Go), y luego comparamos el resultado para saber si la licencia es válida o no:

        res := spdx.ValidateLicense(C.GoString(license))
        if res == nil {
            return true
        } else {
            return false
        }
    }

Cerramos con la definición obligatoria de main:

    func main() {}


Lo usamos

Primer paso, buildear (yo tengo Go 1.6, creo que necesitan 1.5 o superior para poder armar directamente la biblioteca compartida de C, pero no estoy seguro):

    go build -buildmode=c-shared -o spdx.so

Segundo paso, profit!

    $ python2
    >>> import spdx
    >>> spdx.is_valid("GPL-3.0")
    True

Facundo Batista: CDPedia, release y planes


Nueva versión

Hace unos diez días terminé de armar las imágenes para la nueva versión de la CDPedia, la 0.8.4 con contenido actualizado a Junio 2017, y la semana pasada hice los avisos correspondientes en todos lados menos acá.

Ya saben qué es la CDPedia, pero vuelvo a insistir: descárguenla, compártanla, difúndanla, ya que ayuda a que la mayor gente posible acceda a la Wikipedia y al conocimiento que la misma permite.

Vayan a la página oficial para ver cómo descargarla y otra info.

Esta versión no trae demasiadas novedades más que el contenido actualizado (que ya es bastante), pero también renové el contenido de la página de inicio, e hice varias mejoras a la hora de la generación de discos y tarballs, así como también en la calidad del código.

Velita en un festival de jazz en Baradero


Grupo de trabajo

Por otro lado, tengo ganas de mezclar dos cosas que me vienen dando vuelta en la cabeza: la idea de meter más gente a trabajar con CDPedia, y la de hacer algo de mentoring para que gente más nueva aprenda a programar (no sólo a nivel lenguaje, sino también las buenas prácticas, etc.).

Entonces, tengo la idea de armar un grupo de trabajo para CDPedia. Buscar tres o cuatro newbies, más quizás alguien con experiencia que me ayude, y ponernos a trabajar (de forma relajada, remota, pero más o menos constante) en CDPedia.

A nivel del proyecto a varias cosas al alcance de la mano, desde modernización del código y hacerlo más robusto en varias situaciones, hasta mejorar y normalizar los logs, preparar todo para Python 3, corregir bugs, etc.

Claro, la idea también ronda alrededor de las personas, los que participen del proyecto, que aprenderían Python en un nivel básico o intermedio, y obtendrían experiencia en trabajar en grupo de forma remota (y algo de presencial). También se haría foco en usar herramientas como control de versiones, manejo de issues/bugs, y prácticas modernas de desarrollo en general.

En algún punto es parecido a lo que en otro momento se llamó Adopte un Newbie, pero en este caso no hay una relación uno a uno entre mentor y participante, sino que sería un grupo donde cada integrante puede ayudar a otros, todos tutelados o guiados por mí (y como dije arriba, quizás alguien más), todo en un ambiente amigable y "seguro".

Fuente en el Cerro Santa Lucia, Santiago de Chile

Tengo que terminar de redondear la idea, especialmente a nivel operativa: composición del grupo (haría una búsqueda/oferta y luego una selección), formas de comunicación, ¿reuniones presenciales?, etc. También quiero definir la duración de la experiencia: quiero hacerla finita, y luego de alguna manera presentar los resultados con el grupo en alguna conferencia.

Ya les iré contando más novedades.

Facundo Batista: En tu cara, planeta redondo


Ejercicio de Python. El objetivo es tener una serie de timestamps, en función de un registro "tipo cron" que indique periodicidad, desde un punto de partida, hasta "ahora".

El problema es que el "ahora" es de Buenos Aires, mientras que el servidor está en Holanda (o podría estar en cualquier lado).

Lo resolvemos con pytz y croniter. Veamos...

Arranquemos un intérprete interactivo dentro de un virtualenv con las dos libs que acabo de mencionar (y las importamos, además de datetime):

    $ fades -d pytz -d croniter
    *** fades ***  2017-07-26 18:27:20,009  INFO     Hi! This is fades 6.0, automatically managing your dependencies
    *** fades ***  2017-07-26 18:27:20,009  INFO     Need to install a dependency with pip, but no builtin, doing it manually...
    *** fades ***  2017-07-26 18:27:22,979  INFO     Installing dependency: 'pytz'
    *** fades ***  2017-07-26 18:27:24,431  INFO     Installing dependency: 'croniter'
    Python 3.5.2 (default, Nov 17 2016, 17:05:23)
    [GCC 5.4.0 20160609] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import croniter
    >>> import pytz
    >>> import datetime

Veamos que el server tiene horarios "complicados" (en el momento de hacer esto, acá en Buenos Aires son las 18:09):

    >>> datetime.datetime.now()
    datetime.datetime(2017, 7, 26, 23, 9, 51, 476140)
    >>> datetime.datetime.utcnow()
    datetime.datetime(2017, 7, 26, 21, 9, 56, 707279)

Instanciemos croniter, indicando repeticiones todas las horas a las 20 (a propósito, de manera que cuando iteremos desde hace una semana hasta "ahora", debería llegar hasta ayer, porque ahora son las 18 y pico acá, pero justo UTC o la hora del server son más que las 20...):

    >>> cron = croniter.croniter("0 20 * * * ", datetime.datetime(year=2017, month=7, day=20))

Pidamos la hora UTC actual, agregándole metadata de que es UTC, justamente:

    >>> utc_now = pytz.utc.localize(datetime.datetime.utcnow())
    >>> utc_now
    datetime.datetime(2017, 7, 26, 21, 15, 27, 508732, tzinfo=<UTC>)

Pidamos un timezone para Buenos Aires, y el "ahora" de antes pero calculado para esta parte del planeta:

    >>> bsas_tz = pytz.timezone("America/Buenos_Aires")
    >>> bsas_now = utc_now.astimezone(bsas_tz)
    >>> bsas_now
    datetime.datetime(2017, 7, 26, 18, 15, 27, 508732, tzinfo=<DstTzInfo 'America/Buenos_Aires' -03-1 day, 21:00:00 STD>)

Ahora hagamos un loop, pidiendo las fechas a cron y mostrándola, mientras que no sea más que ahora (notar que para compararla, hay que localizarla con el mismo timezone).

    >>> while True:
    ...     next_ts = cron.get_next(datetime.datetime)
    ...     bsas_next_ts = bsas_tz.localize(next_ts)
    ...     if bsas_next_ts > bsas_now:
    ...         break
    ...     print(bsas_next_ts)
    ...
    2017-07-20 20:00:00-03:00
    2017-07-21 20:00:00-03:00
    2017-07-22 20:00:00-03:00
    2017-07-23 20:00:00-03:00
    2017-07-24 20:00:00-03:00
    2017-07-25 20:00:00-03:00

Vemos que tuvimos fechas arrancando el 20 de julio, y tenemos "varios días a las 20hs" hasta ayer, porque todavía no es "hoy a las 20hs". ¡Listo!

Facundo Batista: Análisis de la encuesta "cuál evento"


El último día de Nerdear.la armá una encuesta que llamé "¿Cuál evento?", y la empecé a desparramar por las redes.

La pregunta principal era ¿A qué evento te interesería asistir?

  • PyCon: Conferencia nacional de Python, gratis, con charlas de nivel internacional. Dura 2 o 3 días, sucede una vez por año en algún lugar del país (el próximo es en Córdoba).
  • PyDay: Conferencia local de Python, gratis, con charlas más introductoria. Dura un día, puede haber varios en un año y puede ser en tu ciudad o cerca.
  • PyCamp: Hacking space durante cuatro días haciendo Python o lo que se te cante. Es pago (ya que tenemos el lugar y las comidas todo incluido). Sucede una vez al año en algún lugar del país (el próximo es en Baradero).
  • Consultorio Python: Un afteroffice nerd donde podés traer tu problema de Python y lo resolvemos entre todos. Es en algún lugar con proyector o tele, donde podamos charlar y tomar algo. Se puede hacer varias veces al año, en cualquier ciudad.
  • Reunión Social: Nos juntamos en un bar y charlamos de cualquier cosa (incluso de Python y PyAr, especialmente si hay novedades del grupo o cosas que decidir). Se puede hacer varias veces al año, en cualquier ciudad.
  • Meetup corto: Mini-conferencia de un par de horas para ver dos o tres charlas/presentaciones cortas, con algún espacio social también. Se puede hacer varias veces al año, en cualquier ciudad.
  • Meetup largo: Mezcla de mini-conferencia y sprint. Se hace normalmente un sábado, con algunas charlas a la mañana y un espacio de trabajo/hackeo a la tarde. Se puede hacer varias veces al año, en cualquier ciudad.
  • Sprint: Espacio de trabajo (sobre uno o más proyectos), normalmente un sábado para tener varias horas. Se puede hacer varias veces al año, en cualquier ciudad.


Luego también preguntaba ¿En qué ciudad vivís?, ¿A qué otras ciudades cercanas irías para un evento que dure un día o una tarde?, y dejaba un campo para comentarios.

Obtuve 169 respuestas. Bien.

Los eventos que mayormente la gente quiere ir tienen estilo de conferencia, primero PyCon, luego PyDay. Después ya vienen eventos mezcla de codear y charla (pycamp, meetups, sprints).

Notablemente el modelo de "consultorio Python" fue el que menos votos recibió, igual queremos probar esto al menos una vez, para ver como sale...

El siguiente gráfico es el que me da el form para ver los resultados, está recortado excluyendo opciones que no estaban en la encuesta original (de cualquier manera acá está la planilla con toda la data)

Cual evento

La distribución de votantes es la esperable en función de la (lamentable) centralización de nuestro pais: muchos de CABA y Buenos Aires, bastantes de otras ciudades grandes (Córdoba, Rosario, Santa Fe, La Plata), algunos de otros lados. En general la gente está dispuesta a viajar para los eventos.

A nivel comentarios, los más notables son los que reproduzco acá...

    Me parece que estaría bueno usar internet para acortar distancias, los
    que estamos lejos sabemos que todo pasa en Bs.As. o alrededores (y es
    lógico por una cuestion de cantidad de asistentes). Me encantaría que
    halla un taller online o un PyDayOnline o algo así.
   
    Algunos eventos no los seleccioné por mi nivel de conocimiento, más
    adelante sí me gustaría participar. El que más me gusta es pycamp (esta
    letra gris no se ve, je)
   
    Es notable la ausencia de meetups como en otras comunidades, lugar: una
    empresa, aporte de bebidas y comida, dos conferencias, lightning talks...
    Un PyCamp es demasiado inmersivo y generalmente, para mi, muy lejos. Lo
    bueno de las meetups, es que cumplen con la regla agil de los "dos pies":
    en cualquier momento, te podes ir, caminando :-)
   
    Estaria muy bueno mas charlas jornadas, hackaton de python cientifico
   
    La pycon debería hacerse en días de semana y/o sin coincidir con fin de
    semana largo.  Mucha gente usa Python en su trabajo y puede ir. En cambio
    un fin de semana (y más si es largo) choca con el necesario descanso y
    espacio para la vida personal
   
    Los que somos del interior aprovechariamos más  los eventos que tienen
    mas días.
   
    Me gustan más los eventos en donde todos hablan a los eventos que tienen
    charlas predefinidas, ya que de esa manera todos intercambiamos ideas y
    se pueden escuchar muchas ideas y opiniones.
   
    Me gustaría que exista información acerca de vuelos disponibles, hoteles
    cercanos al evento y costos mínimos de bus, tren, taxi para movilización
    en los lugares que se desarrolla los eventos

En fin, acá está toda la data si quieren hacer más análisis o buscar algún dato puntual.

Roberto Alsina: New mini-project: Gyro

History

Facubatista: ralsina, yo, vos, cerveza, un local-wiki-server-hecho-en-un-solo-.py-con-interfaz-web en tres horas, pensalo

Facubatista: ralsina, you, me, beer, a local-wiki-server-done-in-one-.py-with-web-interface in three hours, think about it

/images/gyro-1.thumbnail.png

The next day.

So, I could not get together with Facu, but I did sort of write it, and it's Gyro. [1]

Technical Details

Gyro has two parts: a very simple backend, implemented using Sanic [2] which does a few things:

  • Serve static files out of _static/
  • Serve templated markdown out of pages/
  • Save markdown to pages/
  • Keep an index of file contents updated in _static/index.js

The other part is a webpage, implemnted using Bootstrap [3] and JQuery [4]. That page can:

  • Show markdown, using Showdown [5]
  • Edit markdown, using SimpleMDE [6]
  • Search in your pages using Lunr [7]

And that's it. Open the site on any URL that doesn't start with _static and contains only letters and numbers:

  • http://localhost:8000/MyPage : GOOD
  • http://localhost:8000/MyDir/MyPage: BAD
  • http://localhost:8000/__foobar__: BAD

At first the page will be sort of empty, but if you edit it and save it, it won't be empty anymore. You can link to other pages (even ones you have not created) using the standard markdown syntax: [go to FooBar](FooBar)

There is really not much else to say about it, if you try it and find bugs, file an issue and as usual patches are welcome.


[1] Why Gyro? Gyros are delicious fast food. Wiki means quick. Also, I like Gyros. to check it out. So, since this was a toy project, why not?
[2] Why Sanic? Ever since Alejandro Lozanoff mentioned a flask-like framework done with the intention to be fast and async I wanted
[3] Why bootstrap? I know more or less what it does, and the resulting page is not totally horrible.
[4] Why JQuery? It's easy, small and I sort of know how it works.
[5] Why Showdown? It's sort of the standard to show markdown on the web.
[6] Why SimpleMDE? It looks and works awesome!
[7] Why Lunr? It works and is smaller than Tipue which is the only other similar thing I know.