show 316 results

Blog entries

  • Logilab à PyConFR 2012 - compte rendu

    2012/10/09 by Alain Leufroy
    http://awesomeness.openphoto.me/custom/201209/4ed140-pycon3--1-of-37-_870x550.jpg

    Logilab était à la conférence PyConFR qui a pris place à Paris il y a deux semaines.

    Nous avons commencé par un sprint pylint, coordonné par Boris Feld, où pas mal de volontaires sont passés pour traquer des bogues ou ajouter des nouvelles fonctionnalités. Merci à tous!

    Pour ceux qui ne connaissent pas encore, pylint est un utilitaire pratique que nous avons dans notre forge. C'est un outil très puissant d'analyse statique de scripts python qui aide à améliorer/maintenir la qualité du code.

    Par la suite, après les "talks" des sponsors¸ où vous auriez pu voir Olivier, vous avons pu participer à quelques tutoriels et présentations vraiment excellentes. Il y avait des présentations pratiques avec, entre autres, les tests, scikit-learn ou les outils pour gérer des services (Cornice, Circus). Il y avait aussi des retours d'information sur le processus de développement de CPython, le développement communautaire ou un supercalculateur. Nous avons même pu faire de la musique avec python et un peu d'"embarqué" avec le Raspberry Pi et Arduino !

    Nous avons, avec Pierre-Yves, proposé deux tutoriels d'introduction au gestionnaire de versions décentralisé Mercurial. Le premier tutoriel abordait les bases avec des cas pratiques. Lors du second tutoriel, que l'on avait prévu initialement dans la continuité du premier, nous avons finalement abordé des utilisations plus avancées permettant de résoudre avec énormément d'efficacité des problématiques quotidiennes, comme les requêtes sur les dépôts, ou la recherche automatique de régression par bissection. Vous pouvez retrouver le support avec les exercices .

    Pierre-Yves a présenté une nouvelle propriété importante de Mercurial: l'obsolescence. Elle permet de mettre en place des outils d'édition d'historique en toute sécurité ! Parmi ces outils, Pierre-Yves a écrit une extension mutable-history qui vous offre une multitude de commandes très pratiques.

    La présentation est disponible en PDF et en consultation en ligne sur slideshare. Nous mettrons bientôt la vidéo en ligne.

    http://www.logilab.org/file/107770?vid=download

    Si le sujet vous intéresse et que vous avez raté cette présentation, Pierre-Yves reparlera de ce sujet à l'OSDC.

    Pour ceux qui en veulent plus, Tarek Ziadé à mis à disposition des photos de la conférence ici.


  • PyLint 0.26 is out

    2012/10/08 by Sylvain Thenault

    I'm very pleased to announce new releases of Pylint and underlying ASTNG library, respectivly 0.26 and 0.24.1. The great news is that both bring a lot of new features and some bug fixes, mostly provided by the community effort.

    We're still trying to make it easier to contribute on our free software project at Logilab, so I hope this will continue and we'll get even more contritions in a near future, and an even smarter/faster/whatever pylint!

    For more details, see ChangeLog files or http://www.logilab.org/project/pylint/0.26.0 and http://www.logilab.org/project/logilab-astng/0.24.1

    So many thanks to all those who made that release, and enjoy!


  • Rencontre LLVM à l'IRILL

    2012/09/27 by Damien Garaud
    IRILL-logo LLVM-logo

    L'IRILL , Initiative de Recherche et Innovation sur le Logiciel Libre, organisait une rencontre LLVM mardi 25 septembre à Paris dans les locaux de l'INRIA Place d'Italie dans le 13e arrondissement. Les organisateurs, Arnaud de Grandmaison, Duncan Sands, Sylvestre Ledru et Tobias Grosser ont chaleureusement accueilli environ 8 personnes dont 3 de Logilab.

    L'annonce de l'IRILL indiquait une rencontre informelle avec une potentielle Bug Squashing Party sur Clang. Nous n'avons pas écrasé d'insectes mais avons plutôt discuté: discussions techniques, petits trolls, échanges de connaissances, d'outils et d'expériences accompagnés de bières, sodas, gâteaux et pizzas (et une salade solitaire et orpheline qui n'a finie dans le ventre de personne contrairement aux pizzas mexicaines ou quatre fromages).

    LLVM (Low Level Virtual Machine) est un projet développé depuis une dizaine d'années qui propose une collection d'outils et de modules facilement réutilisables pour construire des logiciels orientés langages : interpréteurs, compilateurs, optimiseurs, convertisseurs source-to-source...

    Depuis l'étage d'entrée du code dans le compilateur (ou frontend), en passant par l'optimiseur indépendant de la plate-forme, jusqu'à la génération de code machine (backend) et ce, pour plusieurs architectures (X86, PowerPC, ARM, etc.), le choix de son design en fait un outil tout à fait intéressant. Parmi ces frontends, il y a le fameux Clang (C/C++, Objective-C), GHC (Haskell). Et le projet dragonegg permet de l'utiliser comme plug-in à GCC et donc de bénéficier des différents frontends GCC (Ada, Fortran, etc.).

    De nombreux outils se sont construits autour du framework LLVM : LLDB un débugger, vmkit une implémentation de la machine virtuelle Java et .NET ou encore polly, un projet de recherche dont Tobias est l'un des deux co-fondateurs, qui a pour objectif d'optimiser un programme indépendamment du langage via des polyhedral optimizations.

    Les principaux contributeurs à ce jour sont Apple, Google, des contributeurs "individuels" dont Duncan Sands, suivis par des laboratoires de recherche et autres. Voir l'article de Sylvestre sur "Who is in control of LLVM/Clang ?".

    La clef de voûte de LLVM est sa représentation intermédiaire (IR pour Intermediate Representation). C'est une structure de données qui représente sous forme SSA (Single Static Assignment) les flux de données et de contrôle. Cette forme est plus "haut-niveau" et plus lisible que du code assembleur, elle comporte certaines informations de type par exemple. Elle constitue le pivot de l'infrastructure LLVM : c'est ce que produisent les frontends comme Clang, qui est ensuite passée aux optimiseurs de LLVM et est ensuite consommée par les backends dont le rôle est de les transformer en code machine natif.

    En voici un exemple en C:

    int add(int a)
    {
        return a + 42;
    }
    

    La représentation intermédiaire est donnée via Clang :

    $> clang -S -emit-llvm add_function.c -o add_function.ll

    L'extension *.ll désigne des "fichiers IR" et le résultat donne:

    define i32 @add(i32 %a) nounwind uwtable {
      %1 = alloca i32, align 4
      store i32 %a, i32* %1, align 4
      %2 = load i32* %1, align 4
      %3 = add nsw i32 %2, 42
      ret i32 %3
    }
    

    Cette représentation peut ensuite être bitcodée, assemblée ou compilée. Voir les différentes commandes LLVM pour assembler, désassembler, optimiser, linker, exécuter, etc.

    Une autre utilisation intéressante de cette infrastructure est l'utilisation de la bibliothèque Clang pour parser du code C/C++ afin de parcourir l'Abstract Syntax Tree (AST). Ceci offre notamment de belles possibilités d'introspection. Google a d'ailleurs rédigé un tutoriel sur les plugins Clang et ses possibilités via l'API de Clang, notamment la classe ASTConsumer. Il est possible de parcourir l'ensemble des constructeurs, de connaître la propriété d'une classe (abstraite ou non), parcourir les membres d'une classe, etc. Avant de partir, nous parlions de la possiblité de propager un changement d'API à travers toutes les bibliothèques qui en dépendent, soit la notion de patch sémantique.

    Nous avons aussi profité pour parler un peu du langage Julia ou de Numba.

    Pythonistes convaincus, nous connaissions déjà un peu Numba, projet de Travis Oliphant, contributeur NumPy et papa de SciPy. Ce projet utilise l'infrastructure LLVM pour compiler du byte-code Python en code machine (Just In Time compilation), en particulier pour NumPy et SciPy. Les exemples montrent comment passer d'une fonction Python qui traite un tableau NumPy à une fonction compilée Just In Time. Extrait issu d'un des exemples fournis par le projet Numba :

    from numba import d
    from numba.decorators import jit as jit
    
    def sum2d(arr):
        M, N = arr.shape
        result = 0.0
        for i in range(M):
            for j in range(N):
                result += arr[i,j]
        return result
    
    csum2d = jit(ret_type=d, arg_types=[d[:,:]])(sum2d)
    

    Oui, la fonction sum2d n'est pas optimale et très "naïve". Néanmoins, la fonction compilée va près de 250 fois plus vite ! Numba utilise les bindings LLVM pour Python via llvm-py afin de passer du code Python, à l'Intermediate Representation pour ensuite utiliser les fonctionnalités JIT d'LLVM.

    Entre programmeurs de langages à typage statique, nous avons aussi parlé de C et d'Ocaml (dont il existe des bindings pour LLVM) et mentionné de beaux projets tels que PIPS et Coccinelle.

    Pour finir, nous savons maintenant prononcer Clang. On dit "klang" et non "cilangue". Nous avons appris que gdb avait son propre parser et n'utilisait pas le parser de GCC. Rappelons-le, l'un des grands avantages de LLVM & Clang face à GCC est sa modularité et la possibilité d'utiliser une des pierres qui forme l'édifice pour construire sa propre maison.

    Nous finissons ce billet par une citation. David Beazley disait lors de sa présentation à Eursoscipy 2012 :

    The life is too short to write C++.

    Certes. Mais qu'on le veuille ou qu'on le doive, autant se servir d'outils biens pensés pour nous faciliter la vie.

    Encore merci aux organisateurs pour cette rencontre et à la prochaine.

    Quelques références


  • Profiling tools

    2012/09/07 by Alain Leufroy

    Python

    Run time profiling with cProfile

    Python is distributed with profiling modules. They describe the run time operation of a pure python program, providing a variety of statistics.

    The cProfile module is the recommended module. To execute your program under the control of the cProfile module, a simple form is

    $ python -m cProfile -s cumulative mypythonscript.py
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
          16    0.055    0.003   15.801    0.988 __init__.py:1(<module>)
           1    0.000    0.000   11.113   11.113 __init__.py:35(extract)
         135    7.351    0.054   11.078    0.082 __init__.py:25(iter_extract)
    10350736    3.628    0.000    3.628    0.000 {method 'startswith' of 'str' objects}
           1    0.000    0.000    2.422    2.422 pyplot.py:123(show)
           1    0.000    0.000    2.422    2.422 backend_bases.py:69(__call__)
           ...
    

    Each column provides information about time execution of every function calls. -s cumulative orders the result by descending cumulative time.

    Note:

    You can profile a particular python function such as main()

    >>> import profile
    >>> profile.run('main()')
    

    Graphical tools to show profiling results

    Even if report tools are included in cProfile profiler, it can be interesting to use graphical tools. Most of them work with a stat file that can be generated by cProfile using the -o filepath option.

    Below are some of available graphical tools that we tested.

    Gpro2Dot

    is a python based tool that allows to transform profiling results output into a picture containing the call tree graph (using graphviz). A typical profiling session with python looks like this:

    $ python -m cProfile -o output.pstats mypythonscript.py
    $ gprof2dot.py -f pstats output.pstats | dot -Tpng -o profiling_results.png
    
    http://wiki.jrfonseca.googlecode.com/git/gprof2dot.png

    Each node of the output graph represents a function and has the following layout:

    +----------------------------------+
    |   function name : module name    |
    | total time including sub-calls % |  total time including sub-calls %
    |    (self execution time %)       |------------------------------------>
    |  total number of self calls      |
    +----------------------------------+
    

    Nodes and edges are colored according to the "total time" spent in the functions.

    Note:The following small patch let the node color correspond to the execution time and the edge color to the "total time":
    diff -r da2b31597c5f gprof2dot.py
    --- a/gprof2dot.py      Fri Aug 31 16:38:37 2012 +0200
    +++ b/gprof2dot.py      Fri Aug 31 16:40:56 2012 +0200
    @@ -2628,6 +2628,7 @@
                     weight = function.weight
                 else:
                     weight = 0.0
    +            weight = function[TIME_RATIO]
    
                 label = '\n'.join(labels)
                 self.node(function.id,
    
    PyProf2CallTree

    is a script to help visualizing profiling data with the KCacheGrind graphical calltree analyzer. This is a more interactive solution than Gpro2Dot but it requires to install KCacheGrind. Typical usage:

    $ python -m cProfile -o stat.prof mypythonscript.py
    $ python pyprof2calltree.py -i stat.prof -k
    

    Profiling data file is opened in KCacheGrind with pyprof2calltree module, whose -k switch automatically opens KCacheGrind.

    http://kcachegrind.sourceforge.net/html/pics/KcgShot3Large.gif

    There are other tools that are worth testing:

    • RunSnakeRun is an interactive GUI tool which visualizes profile file using square maps:

      $ python -m cProfile -o stat.prof mypythonscript.py
      $ runsnake stat.prof
      
    • pycallgraph generates PNG images of a call tree with the total number of calls:

      $ pycallgraph mypythonscript.py
      
    • lsprofcalltree also use KCacheGrind to display profiling data:

      $ python lsprofcalltree.py -o output.log yourprogram.py
      $ kcachegrind output.log
      

    C/C++ extension profiling

    For optimization purpose one may have python extensions written in C/C++. For such modules, cProfile will not dig into the corresponding call tree. Dedicated tools must be used (they are most part of Python) to profile a C++ extension from python.

    Yep

    is a python module dedicated to the profiling of compiled python extension. It uses the google CPU profiler:

    $ python -m yep --callgrind mypythonscript.py
    

    Memory Profiler

    You may want to control the amount of memory used by a python program. There is an interesting module that fits this need: memory_profiler

    You can fetch memory consumption of a program over time using

    >>> from memory_profiler import memory_usage
    >>> memory_usage(main, (), {})
    

    memory_profiler can also spot lines that consume the most using pdb or IPython.

    General purpose Profiling

    The Linux perf tool gives access to a wide variety of performance counter subsystems. Using perf, any execution configuration (pure python programs, compiled extensions, subprocess, etc.) may be profiled.

    Performance counters are CPU hardware registers that count hardware events such as instructions executed, cache-misses suffered, or branches mispredicted. They form a basis for profiling applications to trace dynamic control flow and identify hotspots.

    You can have information about execution times with:

    $ perf stat -e cpu-cycles,cpu-clock,task-clock python mypythonscript.py
    

    You can have RAM access information using:

    $ perf stat -e cache-misses python mypythonscript.py
    

    Be careful about the fact that perf gives the raw value of the hardware counters. So, you need to know exactly what you are looking for and how to interpret these values in the context of your program.

    Note that you can use Gpro2Dot to get a more user-friendly output:

    $ perf record -g python mypythonscript.py
    $ perf script | gprof2dot.py -f perf | dot -Tpng -o output.png
    

  • Sprint PyLint @ PyConFr 2012

    2012/08/20 by Sylvain Thenault

    Un sprint PyLint est organisé dans le cadre de la conférence PyConFR, les 13 et 14 septembre à Paris. Si vous voulez améliorer PyLint, c'est l'occasion : venez avec vos bugs et repartez sans !

    Les débutants sont bienvenus, une introduction au code de Pylint sera réalisée en début de sprint. Une expérience avec le module ast de la librairie standard est un plus.

    Croissants et café offerts par l'organisation, merci de vous inscrire pour faciliter la logistique. Voir avec Boris pour plus d'informations (merci à lui !)


  • Réseau social ouvert et distribué avec CubicWeb

    2012/07/18 by Nicolas Chauvat

    Qu'est-ce qu'un réseau social ?

    • descriptions de personnes (profil, histoire, etc)
    • liens avec les autres membres (carnet adresses, etc)
    • création de groupes
    • partage de contenu (photos, vidéos, présentations, etc)
    • discussion (blog, commentaires, forums, messages, microblog)
    • mise en relation (boulot, ludo, dodo, etc)
    • recommandation (lien, livre, achat, film, music, etc)
    • présence (fait quoi, avec qui, où, etc)

    Et l'interopérabilité ?

    • nombreuses applications / plate-formes
    • en majorité centralisées et fermées
    • ouverture progressive: protocoles et API en cours de dév
    • réseaux ouverts et distribués: appleseed, diaspora, onesocialweb, etc.
    • pourrait-on faire autrement ?

    API: openstack

    • découverte / discovery = xrd
    • identité / identity = openid
    • contrôle d'accès / access control = oauth
    • activités / streams = activity streams
    • personnes / people = portable contacts
    • applications = opensocial

    Et en utilisant les standards du Web ?

    Architecture ouverte et distribuée

    • vocabulaires RDF et protocole HTTP + REST
    • chacun son serveur
    • GET et éventuellement copie locale
    • abonnement si nécessaire (pubsub, xmpp, atom ?)
    • permissions gérées localement

    => social semantic network

    Pourquoi CubicWeb ?

    • plate-forme pour web sémantique (semantic web framework)
    • conçu pour avoir composants à assembler
    • chacun peut définir son application sur mesure
    • fait pour publier html et rdf en parallèle
    • fait pour exporter et importer données
    • déjà foaf, skos, sioc, doap, rss, atom, etc.

    Exemple

    • (micro)blog + book + link + file
    • pourrait ajouter: musique, photos, etc.
    • mais aussi: journal, recherche appartement, etc.

    Et ensuite ?

    Il y a bien longtemps...

    • découverte = who et cat /etc/passwd | cut -d: -f1
    • identité = login
    • contrôle accès = chmod, chgrp, su
    • activités = .plan
    • personnes = .addressbook
    • applications = vim ~/public_html/me.html

    Note

    Ce texte a été présenté en août 2010, lors de la conférence française des utilisateurs de Python (PyCon-Fr 2010)


  • PyLint 0.25.2 and related projects released

    2012/07/18 by Sylvain Thenault

    I'm pleased to announce the new release of Pylint and related projects (i.e. logilab-astng and logilab-common)!

    By installing PyLint 0.25.2, ASTNG 0.24 and logilab-common 0.58.1, you'll get a bunch of bug fixes and a few new features. Among the hot stuff:

    • PyLint should now work with alternative python implementations such as Jython, and at least go further with PyPy and IronPython (but those have not really been tested, please try it and provide feedback so we can improve their support)
    • the new ASTNG includes a description of dynamic code it is not able to understand. This is handled by a bitbucket hosted project described in another post.

    Many thanks to everyone who contributed to these releases, Torsten Marek / Boris Feld in particular (both sponsored by Google by the way, Torsten as an employee and Boris as a GSoC student).

    Enjoy!


  • Introducing the pylint-brain project

    2012/07/18 by Sylvain Thenault

    Huum, along with the new PyLint release, it's time to introduce the PyLint-Brain project I've recently started.

    Despite its name, PyLint-Brain is actually a collection of extensions for ASTNG, with the goal of making ASTNG smarter (and this directly benefits PyLint) by describing stuff that is too dynamic to be understood automatically (such as functions in the hashlib module, defaultdict, etc.).

    The PyLint-Brain collection of extensions is developped outside of ASTNG itself and hosted on a bitbucket project to ease community involvement and to allow distinct development cycles. Basically, ASTNG will include the PyLint-Brain extensions, but you may use earlier/custom versions by tweaking your PYTHONPATH.

    Take a look at the code, it's fairly easy to contribute new descriptions, and help us make pylint smarter!


  • Debian science sprint and workshop at ESRF

    2012/06/22 by Julien Cristau

    esrf debian

    From June 24th to June 26th, the European Synchrotron organises a workshop centered around Debian. On Monday, a number of talks about the use of Debian in scientific facilities will be featured. On Sunday and Tuesday, members of the Debian Science group will meet for a sprint focusing on the upcoming Debian 7.0 release.

    Among the speakers will be Stefano Zacchiroli, the current Debian project leader. Logilab will be present with Nicolas Chauvat at Monday's conference, and Julien Cristau at both the sprint and the conference.

    At the sprint we'll be discussing packaging of scientific libraries such as blas or MPI implementations, and working on polishing other scientific packages, such as python-related ones (including Salome on which we are currently working).


  • A Python dev day at La Cantine. Would like to have more PyCon?

    2012/06/01 by Damien Garaud
    http://www.logilab.org/file/98313?vid=download http://www.logilab.org/file/98312?vid=download

    We were at La Cantine on May 21th 2012 in Paris for the "PyCon.us Replay session".

    La Cantine is a coworking space where hackers, artists, students and so on can meet and work. It also organises some meetings and conferences about digital culture, computer science, ...

    On May 21th 2012, it was a dev day about Python. "Would you like to have more PyCon?" is a french wordplay where PyCon sounds like Picon, a french "apéritif" which traditionally accompanies beer. A good thing because the meeting began at 6:30 PM! Presentations and demonstrations were about some Python projects presented at PyCon 2012 in Santa Clara (California) last March. The original pycon presentations are accessible on pyvideo.org.

    PDB Introduction

    By Gael Pasgrimaud (@gawel_).

    pdb is the well-known Python debugger. Gael showed us how to easily use this almost-mandatory tool when you develop in Python. As with the gdb debugger, you can stop the execution at a breakpoint, walk up the stack, print the value of local variables or modify temporarily some local variables.

    The best way to define a breakpoint in your source code, it's to write:

    import pdb; pdb.set_trace()
    

    Insert that where you would like pdb to stop. Then, you can step trough the code with s, c or n commands. See help for more information. Following, the help command in pdb command-line interpreter:

    (Pdb) help
    
    Documented commands (type help <topic>):
    ========================================
    EOF    bt         cont      enable  jump  pp       run      unt
    a      c          continue  exit    l     q        s        until
    alias  cl         d         h       list  quit     step     up
    args   clear      debug     help    n     r        tbreak   w
    b      commands   disable   ignore  next  restart  u        whatis
    break  condition  down      j       p     return   unalias  where
    
    Miscellaneous help topics:
    ==========================
    exec  pdb
    

    It is also possible to invoke the module pdb when you run a Python script such as:

    $> python -m pdb my_script.py
    

    Pyramid

    http://www.logilab.org/file/98311?vid=download

    By Alexis Metereau (@ametaireau).

    Pyramid is an open source Python web framework from Pylons Project. It concentrates on providing fast, high-quality solutions to the fundamental problems of creating a web application:

    • the mapping of URLs to code ;
    • templating ;
    • security and serving static assets.

    The framework allows to choose different approaches according the simplicity//feature tradeoff that the programmer need. Alexis, from the French team of Services Mozilla, is working with it on a daily basis and seemed happy to use it. He told us that he uses Pyramid more as web Python library than a web framework.

    Circus

    http://www.logilab.org/file/98316?vid=download

    By Benoit Chesneau (@benoitc).

    Circus is a process watcher and runner. Python scripts, via an API, or command-line interface can be used to manage and monitor multiple processes.

    A very useful web application, called circushttpd, provides a way to monitor and manage Circus through the web. Circus uses zeromq, a well-known tool used at Logilab.

    matplotlib demo

    This session was a well prepared and funny live demonstration by Julien Tayon of matplotlib, the Python 2D plotting library . He showed us some quick and easy stuff.

    For instance, how to plot a sinus with a few code lines with matplotlib and NumPy:

    import numpy as np
    import matplotlib.pyplot as plt
    
    fig = plt.figure()
    ax = fig.add_subplot(111)
    
    # A simple sinus.
    ax.plot(np.sin(np.arange(-10., 10., 0.05)))
    fig.show()
    

    which gives:

    http://www.logilab.org/file/98315?vid=download

    You can make some fancier plots such as:

    # A sinus and a fancy Cardioid.
    a = np.arange(-5., 5., 0.1)
    ax_sin = fig.add_subplot(211)
    ax_sin.plot(np.sin(a), '^-r', lw=1.5)
    ax_sin.set_title("A sinus")
    
    # Cardioid.
    ax_cardio = fig.add_subplot(212)
    x = 0.5 * (2. * np.cos(a) - np.cos(2 * a))
    y = 0.5 * (2. * np.sin(a) - np.sin(2 * a))
    ax_cardio.plot(x, y, '-og')
    ax_cardio.grid()
    ax_cardio.set_xlabel(r"$\frac{1}{2} (2 \cos{t} - \cos{2t})$", fontsize=16)
    fig.show()
    

    where you can type some LaTeX equations as X label for instance.

    http://www.logilab.org/file/98314?vid=download

    The force of this plotting library is the gallery of several examples with piece of code. See the matplotlib gallery.

    Using Python for robotics

    Dimitri Merejkowsky reviewed how Python can be used to control and program Aldebaran's humanoid robot NAO.

    Wrap up

    Unfortunately, Olivier Grisel who was supposed to make three interesting presentations was not there. He was supposed to present :

    • A demo about injecting arbitrary code and monitoring Python process with Pyrasite.
    • Another demo about Interactive Data analysis with Pandas and the new IPython NoteBook.
    • Wrap up : Distributed computation on cluster related project: IPython.parallel, picloud and Storm + Umbrella

    Thanks to La Cantine and the different organisers for this friendly dev day.


show 316 results