latest blogs

J'ai eu la chance de participer au premier hackathon BnF qui s'est déroulé les samedi 19 et dimanche 20 novembre. Alors, c'est quoi un hackathon BnF ?

C'est d'abord un lieu ! Un lieu insiprant qui a été ma deuxième maison mon deuxième lieu de travail pendant ces 5 dernières années où j'ai travaillé sur data.bnf.fr.

https://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/BNF_FM_Hall_Est.jpg/512px-BNF_FM_Hall_Est.jpg

Et puis une thématique : mettre en avant le patrimoine de la BnF et inventer de nouveaux usages numériques autour de ses ressources. Pas beaucoup de contraintes si ce n'est de rendre le code disponible sous une licence libre.

Je ne connais pas bien toutes les applications de la BnF et en particulier je ne maîtrise pas tous les services de Gallica (honte à moi !) mais je commence à avoir une certaine idée de ce que sont les données à la BnF, de comment elles sont rangées (je finis même par connaître les zones intermarc et pouvoir comprendre des 100$blagues). Au-delà du projet data.bnf.fr lui-même, la connaissance de ces données, de leur récupération et de leur usage s'est affinée avec mes travaux sur les projets OpenCat, reliures, bp16, et tous les autres passés ou en cours où on a relié des bases de données extérieures aux notices d'autorité de la BnF comme human-music , andrebreton, les registres de la Comédie-Française, libretheatre, prototype biblissima, bientôt des morceaux d'Archives départementales et nationales et j'en oublie certainement. Je partais donc avec l'idée qu'à défaut de réaliser quelque chose, je saurai a minima servir de facilitateur dans la récupération et le traitement des données.

Le hackathon, c'est aussi une ambiance conviviale portée par les quelques 70 participants qui sont venus, la dizaine d'équipes ainsi constituées et tous les agents BnF qui se sont relayés pendant plus de 24h pour répondre à nos questions, nous guider ou redonner un petit coup de boost lorsque la fatigue ou la frustration commençaient à gagner du terrain. Oui parce qu'en fait, on était quand même là pour tenter de produire quelque chose… Pour ma part, j'ai rejoint en début de hackathon le projet porté par Carmen Brando et Francesca Frontini dont le but était de pouvoir extraire de Gallica les tables des matières et les textes OCRisés et procéder à de la reconnaissance d'entités nommées. Plus précisément, il s'agissait de pouvoir retrouver les lieux et personnes cités dans les textes numérisés pour les aligner vers les données de data.bnf.fr. À la différence d'autres projets, nous voulions donc créer de la nouvelle donnée et l'exploiter plutôt que de réutiliser des relations ou indexations déjà présentes dans les catalogues. Si ce chantier aboutissait, les intérêts pourraient être multiples puisqu'on pourrait imaginer une navigation enrichie de cartes ou de nouveaux rebonds, de nouvelles visualisations à partir de statistiques et possiblement soulever de nouvelles questions de recherche sur les textes eux-mêmes.

Relations entre auteurs / visualisation créée par Marine Riguet

Relations entre auteurs / visualisation créée par Marine Riguet

Nous nous sommes plus ou moins répartis en sous-groupes de travail (je simplifie car chacun a en réalité participé de près ou de loin à tout) :

  • Paule, Delphine et Marc qui étaient nos experts littéraires et nous ont aidé à déterminer des corpus de travail pertinents pour tester nos outils,
  • Frédéric, qui avait développé l'outil de traitement linguistique Alix, et qui s'est donc occupé du traitement initial et des annotations linguistiques des textes,
  • Carmen et Francesca, qui avaient écrit le moteur de reconnaissance d'entités nommées REDEN, se sont occupées d'améliorer l'outil pour permettre le traitement du texte annoté et retrouver les concepts databnf et dbpedia de personnes et de lieux,
  • Gaétan, Mehdi (issus de l'équipe Prevu), Jean-Baptiste se sont plus concentrés sur le développement d'une appli JS pour naviguer et visualiser les résultats obtenus,
  • Bruno et moi-même voguions de sous-groupe en sous-groupe pour faciliter la récupération de données, réaliser les divers pré/post-traitements et aussi taper un peu sur la visu.

Le résultat ? Je crois que nous avons été un peu trop ambitieux et il n'est malheureusement pas encore consultable en ligne mais on va tenter d'y travailler dans les jours qui viennent et de rendre le code accessible. Même si ce n'est encore qu'une preuve de concept, on a malgré tout obtenu quelques jolis résultats comme l'affichage d'une carte des lieux mentionnés dans une œuvre avec rebonds interactifs vers les pages correspondantes dans Gallica ou encore des pages de statistiques sur les personnes citées. Tout ça est encore loin d'être industrialisé et il y a évidemment plein de problèmes comme la résilience face à un mauvais OCR (on s'est concentrés sur les textes dont la qualité d'OCRisation était supérieure à 80% d'après Gallica), à l'ancien français ou encore à la gestion propre des personnes ou lieux fictifs vs. réels.

Exemple d'écran de navigation obtenu qui fait le lien entre une carte et un texte OCRisé de Gallica

En tout cas, j'ai eu la chance de tomber sur des co-équipiers de luxe et je garderai un excellent souvenir de ces 24h. Pour conclure, j'adresse un grand bravo à gallicarte qui a remporté le prix du jury et à diderotbot qui a trouvé de belles perles dans Gallica qui résonnaient particulièrement bien avec l'actualité.

À l'année prochaine pour la suite j'espère !

blog entry of

J'ai eu l'occasion de participer une nouvelle fois à un forum ouvert lors de la rencontre autour de l'entreprise libérée organisée par l'APAP et NOÏO (la dernière, c'était à l'Agile Tour Toulouse) . J'en ai fait un petit compte-rendu mais ce n'est pas l'objet de ce billet.

Comme je trouve que le forum ouvert est vraiment un format super pour tirer le meilleur parti d'un groupe de gens indépendamment de la taille du groupe, je vais ici faire un petit rappel des bases (telles qu'elles nous ont été rappellées lors de cette rencontre), qu'on peut ensuite adapter en fonction de ses propres contraintes.

Les principes du forum ouvert (ou Open Space) sont inspirés du fait que dans les conférences, la plupart des choses intéressantes sont dites en off : en discutant entre les conférences, pendant le café, devant la porte, etc. L'idée est donc de transformer la conférence en une grande pause avec des discussions libres, en petit groupe, autour d'un thème donné et avec les quatre principes suivants pour mettre tout le monde à l'aise :

  • toutes les personnes présentes sont les bonnes personnes,
  • ce qui arrive est ce qui pouvait arriver,
  • quelque soit le moment où ça commence, c'est le bon moment,
  • et quand c'est fini, c'est fini.
https://www.logilab.org/file/9306538/raw/20161121_194647.jpg

Partant de ces bases, un forum ouvert se déroule en quatre phases :

  1. introduction du sujet et des principes du forum ouvert énoncés ici,
  2. proposition et éventuellement sélection des sujets,
  3. plusieurs rounds de discussions sur les sujets choisis,
  4. choix d'actions et clôture.

Une fois le sujet introduit, voici le détail du déroulement des étapes suivantes...

L'émergence des sujets

Dans cette première phase, chacun est invité à proposer un sujet qu'il va écrire en gros sur une feuille en y indiquant également son nom. Cette feuille sera affichée sur un tableau qu'on nomme la place du marché, accompagnée d'une indication de l'heure et du lieu où aura lieu cette discussion.

Pour cette indication, l'organisateur aura au préalable préparé une grille d'emploi du temps déduite :

  • du nombre de discussions en parallèle (en fonction de l'espace ou des tables disponibles ainsi que du nombre de personnes présentes - compter entre 5 et 10 personnes max par groupe),
  • de la durée et le nombre de créneaux successifs (au moins 40 minutes pour un créneau, le temps passe vite !).

À partir de ces informations on obtient une grille horaire dans laquelle les propositions pourront être placées, ainsi accompagnée d'un lieu (en général un numéro de table) et d'un créneau horaire.

https://www.logilab.org/file/9306522/raw/20161121_194729.jpg

On peut apparemment tabler sur une proposition de sujet pour deux personnes en moyenne. Si plusieurs propositions sont similaires, il est possible de les recouper si les porteurs du sujet le souhaitent. Enfin s'il est nécessaire de faire une sélection, on peut demander aux participants de "s'inscrire" sur les sujets afin de voir lesquels sont les moins suivis.

Le temps des discussions

Et c'est parti pour le premier round de discussion ! Chaque porteur de sujet s'installe à sa table, y indique clairement le sujet discuté (on laisse l'affichage général en place pour les retardataires et promeneurs) et attend d'être rejoint par d'autres personnes également intéressées par ce sujet. Il a deux responsabilités :

  • introduire le sujet,
  • s'assurer qu'un compte-rendu sera écrit (mais pas forcément par lui).

Animer la discussion n'en fait pas parti.

Pendant les discussions, on peut ajouter :

  • la loi des deux pieds : chacun est libre s'il en ressent l'envie pour une raison ou pour une autre de quitter sa table pour aller s'installer sur une autre,
  • les abeilles qui butinent de tables en tables, sans jamais vraiment s'installer mais en permettant d'essaimer l'information d'une table à l'autre,
  • les papillons qui papillonnent un peu en marge du processus, mais il n'est pas rare d'en voir émerger des choses.
https://www.logilab.org/file/9306530/raw/20161121_194658.jpg

Une dizaine de minutes avant la fin du créneau, l'organisateur indique qu'il est temps de s'assurer que le compte-rendu de la discussion sera fait. Enfin à la fin du temps imparti, chaque table va afficher son compte-rendu sur le grand journal.

https://www.logilab.org/file/9306515/raw/20161121_212456.jpg

Je trouve qu'il est intéressant de réserver un créneau à ce moment là pour qu'une personne par table présente ce compte-rendu en quelques minutes, car il est parfois difficile de se contenter de ce qui est écrit ou dessiné.

Après on enchaîne rapidement sur le round suivant, et ainsi de suite.

La clôture

À ce moment là, tout le monde commence à être bien détendu, en confiance, et à connaître au moins une partie des participants. Afin de faire avancer la cause discutée, on va effectuer une dernier round de propositions / discussions dont l'objectif est de dégager des actions réalistes à court terme. Sur le modèle des étapes précédente, les participants sont invités à proposer une action qu'ils ont envie de tirer (ou de voir tirer) avec d'autres. Ils l'énoncent et l'affichent sur le marché aux actions.

Une fois toutes les actions proposées, les personnes intéressées par une action donnée se regroupent et structurent une action qui sera énoncée devant l'assistance une fois le temps imparti écoulé. Si possible, l'organisateur effectuera un suivi de ces actions après l'évènement.

https://www.logilab.org/file/9306297/raw/20161121_214833.jpg

Il est ensuite temps de se féliciter, de se remercier, d'annoncer la suite ou toute autre chose utile avant de se quitter.

Les photos sont tirées de l'évènement sus-cité, merci aux organisateurs et en particulier ici aux facilitateurs graphiques !

blog entry of

J'ai eu l'occasion de participer à une rencontre autour de l'entreprise libérée organisée par l'APAP et NOÏO sur Toulouse. Voici quelques notes pour la postérité.

https://www.logilab.org/file/9306297/raw/20161121_214833.jpg

La première partie de cette rencontre était la diffusion du documentaire E 3.0, Une entreprise humaniste qui présente les 6 premiers mois de la "libération" d'Averia, une entreprise de miroiterie d'ile de france. Le réalisateur était présent et nous a annoncé en amont de la projection son parti pris volontaire pour l'entreprise libérée (ce qui n'est pas pour me déplaire). J'ai trouvé ce documentaire intéressant de par l'aspect "témoignage sur le vif" et par le suivi sur quelques mois de cette phase critique de transformation. Ça donne envie de savoir où il en sont maintenant (la période filmée est le second semestre 2015).

La seconde partie s'est déroulée sous la forme d'un forum ouvert. Au delà des sujets de départ que j'avais choisi, cela m'a surtout permis d'échanger avec d'autres personnes dont l'entreprise est plus ou moins avancée sur le chemin de la libération (j'ai du mal avec ce terme que je trouve un peu galvaudé mais bon). J'y ai notamment rencontré une dirigeante d'une société de pose de parquets (Erah), en voie de "libération" depuis 5 ans. Celle-ci a pour le moins étonné tout le monde lorsqu'elle nous a appris que ses salariés avaient décidés ensemble d'être tous payés pareils, indépendamment de leur expérience (mais légèrement au dessus des prix du marché même pour les expérimentés), ou encore que la société finançait à ses salariés des stages sur leur temps de travail, indépendamment de l'intérêt du sujet pour elle. J'ai également discuté avec la dirigeante de Fun and fly qui gère son entreprise d'une dizaine de personnes dans la veine de l'entreprise libérée sans le savoir jusqu'ici. Non sans similitude avec Logilab, où nous avons grandi depuis 2000 avec bon nombre de principes aujourd'hui regroupés sous la bannière de l'entreprise libérée.

https://www.logilab.org/file/9306355/raw/20161121_222142.jpg

La soirée s'est conclut pour moi avec le directeur de Web-Atrio qui devrait prochainement inviter le petit groupe que nous avons formé à un déjeuner ou diner afin d'aller plus loin dans les échanges autour de nos avancées et expérimentations respectives, élément qui est apparu essentiel à chacun, même si nous n'espérons pas y trouver de recettes miracles s'appliquant à tout le monde.

Pour aller plus loin, le lecteur intéressé pourra :

  • regarder cette conférence d'Isaac Getz qui m'a été recommandée pendant la soirée (à Logilab Toulouse nous en avons regardé une de Frédéric Laloux que je recommende également si vous n'avez pas lu son livre),
  • lire une bande dessinée à ce sujet,
  • suivre ce qu'il se passe du côté de l'association MOM21, qui devrait notamment créer une antenne Sud-Ouest et organiser une journée à ce sujet le 18 janvier prochain (mais je n'ai pas trouvé plus d'info à ce sujet sur leur site).

Merci à tous les organisateurs pour ce moment rondement mené et qui a permis de se rendre compte qu'on n'est pas seul sur le chemin !

blog entry of

SciviJS

2016/10/10 by Martin Renou

Introduction

The goal of my work at Logilab is to create tools to visualize scientific 3D volumic-mesh-based data (mechanical data, electromagnetic...) in a standard web browser. It's a part of the european OpenDreamKit project. Franck Wang has been working on this subject last year. I based my work on his results and tried to improve them.

Our goal is to create widgets to be used in Jupyter Notebook (formerly IPython) for easy 3D visualization and analysis. We also want to create a graphical user interface in order to enable users to intuitively compute multiple effects on their meshes.

As Franck Wang worked with X3DOM, which is an open source JavaScript framework that makes it possible to display 3D scenes using HTML nodes, we first thought it was a good idea to keep on working with this framework. But X3DOM is not very well maintained these days, as can be seen on their GitHub repository.

As a consequence, we decided to take a look at another 3D framework. Our best candidates were:

  • ThreeJS
  • BabylonJS

ThreeJS and BabylonJS are two well-known Open Source frameworks for 3D web visualization. They are well maintained by hundreds of contributors since several years. Even if BabylonJS was first thought for video games, these two engines are interesting for our project. Some advantages of ThreeJS are:

Finally, the choice of using ThreeJS was quite obvious because of its Nodes feature, contributed by Sunag Entertainment. It allows users to compose multiple effects like isocolor, threshold, clip plane, etc. As ThreeJS is an Open Source framework, it is quite easy to propose new features and contributors are very helpful.

ThreeJS

As we want to compose multiple effects like isocolor and threshold (the pixel color correspond to a pressure but if this pressure is under a certain threshold we don't want to display it), it seems a good idea to compose shaders instead of creating a big shader with all the features we want to implement. The problem is that WebGL is still limited (as of the 1.x version) and it's not possible for shaders to exchange data with other shaders. Only the vertex shader can send data to the fragment shader through varyings.

So it's not really possible to compose shaders, but the good news is we can use the new node system of ThreeJS to easily compute and compose a complex material for a mesh.

alternate text

It's the graphical view of what you can do in your code, but you can see that it's really simple to implement effects in order to visualize your data.

SciviJS

With this great tools as a solid basis, I designed a first version of a javascript library, SciviJS, that aims at loading, displaying and analyzing mesh data in a standard web browser (i.e. without any plugin).

You can define your visualization in a .yml file containing urls to your mesh and data and a hierarchy of effects (called block structures).

See https://demo.logilab.fr/SciviJS/ for an online demo.

You can see the block structure like following:

https://www.logilab.org/file/8719790/raw

Data blocks are instantiated to load the mesh and define basic parameters like color, position etc. Blocks are connected together to form a tree that helps building a visual analysis of your mesh data. Each block receives data (like mesh variables, color and position) from its parent and can modify them independently.

Following parameters must be set on dataBlocks:

  • coordURL: URL to the binary file containing coordinate values of vertices.
  • facesURL: URL to the binary file containing indices of faces defining the skin of the mesh.
  • tetrasURL: URL to the binary file containing indices of tetrahedrons. Default is ''.
  • dataURL: URL to the binary file containing data that you want to visualize for each vertices.

Following parameters can be set on dataBlocks or plugInBlocks:

  • type: type of the block, which is dataBlock or the name of the plugInBlock that you want.
  • colored: define whether or not the 3D object is colored. Default is false, object is rendered gray.
  • colorMap: color map used for coloration, available values are rainbow and gray. Default is rainbow.
  • colorMapMin and colorMapMax: bounds for coloration scaled in [0, 1]. Default is (0, 1).
  • visualizedData: data used as input for coloration. If data are 3D vectors available values are magnitude, X, Y, Z, and default is magnitude. If data are scalar values you don't need to set this parameter.
  • position, rotation, scale: 3D vectors representing position, rotation and scale of the object. Default are [0., 0., 0.], [0., 0., 0.] and [1., 1., 1.].
  • visible: define whether or not the object is visible. Default is true if there's no childrenBlock, false otherwise.
  • childrenBlocks: array of children blocks. Default is empty.

As of today, there are 6 types of plug-in blocks:

  • Threshold: hide areas of your mesh based on a variable's value and bound parameters

    • lowerBound: lower bound used for threshold. Default is 0 (representing dataMin). If inputData is under lowerBound, then it's not displayed.
    • upperBound: upper bound used for threshold. Default is 1 (representing dataMax). If inputData is above upperBound, then it's not displayed.
    • inputData: data used for threshold effect. Default is visualizedData, but you can set it to magnitude, X, Y or Z.
  • ClipPlane: hide a part of the mesh by cutting it with a plane

    • planeNormal: 3D array representing the normal of the plane used for section. Default is [1., 0., 0.].
    • planePosition: position of the plane for the section. It's a scalar scaled bewteen -1 and 1. Default is 0.
  • Slice: make a slice of your mesh

    • sliceNormal
    • slicePosition
  • Warp: deform the mesh along the direction of an input vector data

    • warpFactor: deformation factor. Default is 1, can be negative.
    • inputData: vector data used for warp effect. Default is data, but you can set it to X, Y or Z to use only one vector component.
  • VectorField: represent the input vector data with arrow glyphs

    • lengthFactor: factor of length of vectors. Default is 1, can be negative.
    • inputData
    • nbVectors: max number of vectors. Default is the number of vertices of the mesh (which is the maximum value).
    • mode: mode of distribution. Default is volume, you can set it to surface.
    • distribution: type of distribution. Default is regular, you can set it to random.
  • Points: represent the data with points

    • pointsSize: size of points in pixels. Default is 3.
    • nbPoints
    • mode
    • distribution

Using those blocks you can easily render interesting 3D scenes like this:

https://www.logilab.org/file/8571787/raw https://www.logilab.org/file/8572007/raw

Future works

  • Integration to Jupyter Notebook
  • As of today you only can define a .yml file defining the tree of blocks, we plan to develop a Graphical User Interface to enable users to define this tree interactively with drag and drop
  • Support of most file types (for now it only supports binary files)
blog entry of

La semaine dernière, je suis allé au meetup docker sur "Containers' Jungle. Docker, Rocket, RunC, LXD ... WTF ?".

À Logilab, nous sommes parfois un poil déçus par docker (mais utilisateurs comme vous pouvez le voir sur notre blog Developing salt formulas with docker, Running a local salt-master to orchestrate docker containers, Building Docker containers using Salt, Retour sur la journée conteneurs dans le cadre de Open Source Innovation Spring). Du coup nous étions curieux d'aller creuser la piste de rocket et autres technologies de conteneurs.

https://www.logilab.org/file/8445845/raw/global_361217852.jpeg

Nicolas De Loof nous a présentés quelques concepts sous-jacents à docker, mais aussi quelques alternatives et retours d’expérience sur l'utilisateur de docker et de son écosystème. La présentation sera rejouée au devfest Nantes pour ceux qui l'auraient ratée.

Voici quelques liens collectés lors du meetup qui nécessiteraient un peu plus de lecture et d'explications, mais je pose cela tel quel au cas où ce serait utile à d'autres :

Un meetup recommandé pour explorer docker (et ses alternatives?) à Nantes.

blog entry of

blog entry of

ngReact is an Angular module that allows React components to be used in AngularJS applications.

I had to work on enhancing an Angular-based application and wanted to provide the additionnal functionnality as an isolated component that I could develop and test without messing with a large Angular controller that several other people were working on.

Here is my Angular+React "Hello World", with a couple gotchas that were not underlined in the documentation and took me some time to figure out.

To set things up, just run:

$ mkdir angulareacthello && cd angulareacthello
$ npm init && npm install --save angular ngreact react react-dom

Then write into index.html:

<!doctype html>
<html>
     <head>
             <title>my angular react demo</title>
     </head>
     <body ng-app="app" ng-controller="helloController">
             <div>
                     <label>Name:</label>
                     <input type="text" ng-model="person.name" placeholder="Enter a name here">
                     <hr>
                     <h1><react-component name="HelloComponent" props="person" /></h1>
             </div>
     </body>
     <script src="node_modules/angular/angular.js"></script>
     <script src="node_modules/react/dist/react.js"></script>
     <script src="node_modules/react-dom/dist/react-dom.js"></script>
     <script src="node_modules/ngreact/ngReact.js"></script>
     <script>
     // include the ngReact module as a dependency for this Angular app
     var app = angular.module('app', ['react']);

     // define a controller that has the name attribute
     app.controller('helloController', function($scope) {
             $scope.person = { name: 'you' };
     });

     // define a React component that displays "Hello {name}"
     var HelloComponent = React.createClass({
             render: function() {
                     return React.DOM.span(null, "Hello "+this.props.name);
             }
     });

     // tell Angular about this React component
     app.value('HelloComponent', HelloComponent);

     </script>
</html>

I took me time to get a couple things clear in my mind.

<react-component> is not a React component, but an Angular directive that delegates to a React component. Therefore, you should not expect the interface of this tag to be the same as the one of a React component. More precisely, you can only use the props attribute and can not set your react properties by adding more attributes to this tag. If you want to be able to write something like <react-component firstname="person.firstname" lastname="person.lastname"> you will have to use reactDirective to create a specific Angular directive.

You have to set an object as the props attribute of the react-component tag, because it will be used as the value of this.props in the code of your React class. For example if you set the props attribute to a string (person.name instead of person in the above example) , you will have trouble using it on the React side because you will get an object built from the enumeration of the string. Therefore, the above example can not be made simpler. If we had written $scope.name = 'you' we could not have passed it correctly to the react component.

The above was tested with angular@1.5.8, ngreact@0.3.0, react@15.3.0 and react-dom@15.3.0.

All in all, it worked well. Thank you to all the developers and contributors of these projects.

blog entry of

In a previous post we talked about an environment to develop salt formulas. To add some spicy requirements, the formula must now handle multiple target OS (Debian and Centos), have tests and a continuous integration (CI) server setup.

http://testinfra.readthedocs.io/en/latest/_static/logo.png

I started a year ago to write a framework to this purpose, it's called testinfra and is used to execute commands on remote systems and make assertions on the state and the behavior of the system. The modules API provides a pythonic way to inspect the system. It has a smooth integration with pytest that adds some useful features out of the box like parametrization to run tests against multiple systems.

Writing useful tests is not an easy task, my advice is to test code that triggers implicit actions, code that has caused issues in the past or simply test the application is working correctly like you would do in the shell.

For instance this is one of the tests I wrote for the saemref formula

def test_saemref_running(Process, Service, Socket, Command):
    assert Service("supervisord").is_enabled

    supervisord = Process.get(comm="supervisord")
    # Supervisor run as root
    assert supervisord.user == "root"
    assert supervisord.group == "root"

    cubicweb = Process.get(ppid=supervisord.pid)
    # Cubicweb should run as saemref user
    assert cubicweb.user == "saemref"
    assert cubicweb.group == "saemref"
    assert cubicweb.comm == "uwsgi"
    # Should have 2 worker process with 8 thread each and 1 http proccess with one thread
    child_threads = sorted([c.nlwp for c in Process.filter(ppid=cubicweb.pid)])
    assert child_threads == [1, 8, 8]

    # uwsgi should bind on all ipv4 adresses
    assert Socket("tcp://0.0.0.0:8080").is_listening

    html = Command.check_output("curl http://localhost:8080")
    assert "<title>accueil (Référentiel SAEM)</title>" in html

Now we can run tests against a running container by giving its name or docker id to testinfra:

% testinfra --hosts=docker://1a8ddedf8164 test_saemref.py
[...]
test/test_saemref.py::test_saemref_running[docker:/1a8ddedf8164] PASSED

The immediate advantage of writing such test is that you can reuse it for monitoring purpose, testinfra can behave like a nagios plugin:

% testinfra -qq --nagios --hosts=ssh://prod test_saemref.py
TESTINFRA OK - 1 passed, 0 failed, 0 skipped in 2.31 seconds
.

We can now integrate the test suite in our run-tests.py by adding some code to build and run a provisioned docker image and add a test command that runs testinfra tests against it.

provision_option = click.option('--provision', is_flag=True, help="Provision the container")

@cli.command(help="Build an image")
@image_choice
@provision_option
def build(image, provision=False):
    dockerfile = "test/{0}.Dockerfile".format(image)
    tag = "{0}-formula:{1}".format(formula, image)
    if provision:
        dockerfile_content = open(dockerfile).read()
        dockerfile_content += "\n" + "\n".join([
            "ADD test/minion.conf /etc/salt/minion.d/minion.conf",
            "ADD {0} /srv/formula/{0}".format(formula),
            "RUN salt-call --retcode-passthrough state.sls {0}".format(formula),
        ]) + "\n"
        dockerfile = "test/{0}_provisioned.Dockerfile".format(image)
        with open(dockerfile, "w") as f:
            f.write(dockerfile_content)
        tag += "-provisioned"
    subprocess.check_call(["docker", "build", "-t", tag, "-f", dockerfile, "."])
    return tag


@cli.command(help="Spawn an interactive shell in a new container")
@image_choice
@provision_option
@click.pass_context
def dev(ctx, image, provision=False):
    tag = ctx.invoke(build, image=image, provision=provision)
    subprocess.call([
        "docker", "run", "-i", "-t", "--rm", "--hostname", image,
        "-v", "{0}/test/minion.conf:/etc/salt/minion.d/minion.conf".format(BASEDIR),
        "-v", "{0}/{1}:/srv/formula/{1}".format(BASEDIR, formula),
        tag, "/bin/bash",
    ])


@cli.command(help="Run tests against a provisioned container",
             context_settings={"allow_extra_args": True})
@click.pass_context
@image_choice
def test(ctx, image):
    import pytest
    tag = ctx.invoke(build, image=image, provision=True)
    docker_id = subprocess.check_output([
        "docker", "run", "-d", "--hostname", image,
        "-v", "{0}/test/minion.conf:/etc/salt/minion.d/minion.conf".format(BASEDIR),
        "-v", "{0}/{1}:/srv/formula/{1}".format(BASEDIR, formula),
        tag, "tail", "-f", "/dev/null",
    ]).strip()
    try:
        ctx.exit(pytest.main(["--hosts=docker://" + docker_id] + ctx.args))
    finally:
        subprocess.check_call(["docker", "rm", "-f", docker_id])

Tests can be run on a local CI server or on travis, they "just" require a docker server, here is an example of .travis.yml

sudo: required
services:
  - docker
language: python
python:
  - "2.7"
env:
  matrix:
    - IMAGE=centos7
    - IMAGE=jessie
install:
  - pip install testinfra
script:
  - python run-tests.py test $IMAGE -- -v

I wrote a dummy formula with the above code, feel free to use it as a template for your own formula or open pull requests and break some tests.

There is a highly enhanced version of this code in the saemref formula repository, including:

  • Building a provisioned docker image with custom pillars, we use it to run an online demo
  • Destructive tests where each test is run in a dedicated "fresh" container
  • Run Systemd in the containers to get a system close to the production one (this enables the use of Salt service module)
  • Run a postgresql container linked to the tested container for specific tests like upgrading a Cubicweb instance.

Destructive tests rely on advanced pytest features that may produce weird bugs when mixed together, too much magic involved here. Also, handling Systemd in docker is really painful and adds a lot of complexity, for instance some systemctl commands require a running systemd as PID 1 and this is not the case during the docker build phase. So the trade-off between complexity and these features may not be worth.

There is also a lot of quite new tools to develop and test infrastructure code that you could include in your stack like test-kitchen, serverspec, and goss. Choose your weapon and go test your infrastructure code.

blog entry of

https://www.logilab.org/file/248336/raw/Salt-Logo.png

While developing salt formulas I was looking for a simple and reproducible environment to allow faster development, less bugs and more fun. The formula must handle multiple target OS (Debian and Centos).

The first barrier is the master/minion installation of Salt, but fortunately Salt has a masterless mode. The idea is quite simple, bring up a virtual machine, install a Salt minion on it, expose the code inside the VM and call Salt states.

https://www.logilab.org/file/7159870/raw/docker.png

At Logilab we like to work with docker, a lightweight OS-level virtualization solution. One of the key features is docker volumes to share local files inside the container. So I started to write a simple Python script to build a container with a Salt minion installed and run it with formula states and a few config files shared inside the VM.

The formula I was working on is used to deploy the saemref project, which is a Cubicweb based application:

% cat test/centos7.Dockerfile
FROM centos:7
RUN yum -y install epel-release && \
    yum -y install https://repo.saltstack.com/yum/redhat/salt-repo-latest-1.el7.noarch.rpm && \
    yum clean expire-cache && \
    yum -y install salt-minion

% cat test/jessie.Dockerfile
FROM debian:jessie
RUN apt-get update && apt-get -y install wget
RUN wget -O - https://repo.saltstack.com/apt/debian/8/amd64/latest/SALTSTACK-GPG-KEY.pub | apt-key add -
RUN echo "deb http://repo.saltstack.com/apt/debian/8/amd64/latest jessie main" > /etc/apt/sources.list.d/saltstack.list
RUN apt-get update && apt-get -y install salt-minion

% cat test/minion.conf
file_client: local
file_roots:
  base:
    - /srv/salt
    - /srv/formula

And finally the run-tests.py file, using the beautiful click module

#!/usr/bin/env python
import os
import subprocess

import click

@click.group()
def cli():
    pass

formula = "saemref"
BASEDIR = os.path.abspath(os.path.dirname(__file__))

image_choice = click.argument("image", type=click.Choice(["centos7", "jessie"]))


@cli.command(help="Build an image")
@image_choice
def build(image):
    dockerfile = "test/{0}.Dockerfile".format(image)
    tag = "{0}-formula:{1}".format(formula, image)
    subprocess.check_call(["docker", "build", "-t", tag, "-f", dockerfile, "."])
    return tag


@cli.command(help="Spawn an interactive shell in a new container")
@image_choice
@click.pass_context
def dev(ctx, image):
    tag = ctx.invoke(build, image=image)
    subprocess.call([
        "docker", "run", "-i", "-t", "--rm", "--hostname", image,
        "-v", "{0}/test/minion.conf:/etc/salt/minion.d/minion.conf".format(BASEDIR),
        "-v", "{0}/{1}:/srv/formula/{1}".format(BASEDIR, formula),
        tag, "/bin/bash",
    ])


if __name__ == "__main__":
    cli()

Now I can run quickly multiple containers and test my Salt states inside the containers while editing the code locally:

% ./run-tests.py dev centos7
[root@centos7 /]# salt-call state.sls saemref

[ ... ]

[root@centos7 /]# ^D
% # The container is destroyed when it exits

Notice that we could add some custom pillars and state files simply by adding specific docker shared volumes.

With a few lines we created a lightweight vagrant like, but faster, with docker instead of virtualbox and it remain fully customizable for future needs.

blog entry of

Nous avons assisté à la conférence Agile France qui a eu lieu les 16 et 17 juin au Chalet de la Porte Jaune, à Paris.

La grande conférence agile francophone, de la communauté pour la communauté, réalisée dans un lieu exceptionnel proposait d'aborder différents sujets, tels que les méthodes agiles, l'intelligence collective et la facilitation, le développement de logiciels, l'expérience utilisateur d'innovation, les organisations et leur management, etc.

Dans un cadre très agréable permettant de s'échapper de l'ambiance urbaine, notre équipe a pu assister à quelques conférences qui ont attirées notre attention :

Facilitation graphique

Jean-Pierre Bonnafous, qui a participé aux 15 ans de Logilab, a invité les participants à réagir tout au long de ces deux jours et a mis en image leurs retours.

https://www.logilab.org/file/6729471/raw

Toutes les sessions d'Agile France étaient présentées sur une fresque. Nous, nous sommes fans !

https://www.logilab.org/file/6831358/raw

Utilisateur, fais moi mal : la ditacture du test

Emilie-Anne Gerch et Nicolas Moreau ont parlé de l'importance du test utilisateur d'une façon dynamique et ont partagé leur expérience concernant le site d'AXA Assurances avec une approche assez drôle : les designers viennent de Vénus et les développeurs de Mars, les utilisateurs sont, quant à eux, de bons Terriens. Comment ramener sur Terre un designer un peu trop perché ou sortir de sa grotte un développeur pour qui un texte blanc sur fond noir est la norme ?

Grande leçon pour les designers, les développeurs et les chefs de projet, car ceux qui apportent la bonne réponse ce sont les utilisateurs. Selon eux, c'est le jury le plus juste, car ce sont eux qui utilisent le produit final. Les utilisateurs finaux constituent le jury le plus sévère mais le plus juste qui soit. Ils ont des mots parfois crus et des doigts qui cliquent partout sauf là où on avait pensé. :-)

Les tests utilisateur permettent de tester un produit en conditions réelles. Grâce à ceux-ci, il est possible de recueillir des informations utiles pour améliorer le produit. Ils permettent de donner une priorité à différentes fonctionnalités ou idées. Ils permettent de reconnaître les fonctionnalités à conserver (les plus utilisées, les plus demandées...) et celles à supprimer (celles que personne ne voit ou n'utilise). Ils constituent aussi un moyen d'intégrer efficacement l'utilisateur dans la conception.

Quelques points permettant de mieux comprendre la psychologie de l'utilisateur :

L'être humain utilise rarement un outil pour sa fonction primaire. Il est partisan du moindre effort, il bricole et modifie les outils de manière à les adapter à son besoin.

L'utilisateur souhaite avant tout aller à l'essentiel.

Il ne veut pas avoir la sensation de devoir apprendre. Il ne lit pas les manuels, il pioche les informations dont il a besoin lorsque la nécessité se fait sentir.

Il est influencé par l'extérieur (le bruit, un interlocuteur), par son état émotionnel (le stress, la somnolence...) et par son vécu (il calque ses actions sur ce qu'il a déjà pratiqué ailleurs). Son expérience s'étend au delà du numérique.

Il est "techno-aveugle" : il veut avant tout que ça marche, la technique utilisée ne l'intéresse pas.

Il est bienveillant : il aura tendance à se blâmer au lieu de remettre en cause le produit et donne des retours d'expérience très facilement.

Présentation

Un iceberg pour explorer ce qui ne va pas

Certains d'entre-nous ont assisté à la conférence Un iceberg pour explorer ce qui ne va pas, animée par Emmanuel Gaillot et Raphaël Pierquin. La session portait sur la découverte de nos réactions face à un évènement donné.

Cette conférence a démarré par une démonstration pratique basée sur la métaphore de l'iceberg créée par Virginia Satir. Il arrive qu'on agisse et qu'on réagisse d'une manière qui nous surprend ou nous dépasse. Pendant cette session, nous avons exploré ces situations à l'aide d'un exercice inventé par Virginia Satir, basé sur la métaphore de l'iceberg (ce qui est émergé est observable, ce qui est immergé se passe au-dedans). Les participant·e·s ont pu ainsi s'approprier un format de réflexion simple à suivre pour apprendre à mieux se connaître — et possiblement apprendre à mieux s'apprécier.

Raphaël Pierquin a choisi un évènement qu'il a contextualisé en décrivant les comportements de chaque personne impliquée dans son récit, puis a présenté sa stratégie par rapport à cet évènement. Guidé par Emmanuel Gaillot, qui avait au préalable disposé des feuilles de papier au sol sur lesquelles étaient écrits les intitulés de chaque "case" de l'iceberg, il a ensuite déroulé devant l'assemblée toutes les étapes présentes sous le niveau de l'eau de l'iceberg. À chaque changement de "case" dans son récit, Raphaël se déplaçait vers la feuille idoine, établissant ainsi son propre cheminement physique et mental. Nous avons ensuite eu l'occasion de pratiquer par trinôme puis de discuter de cette méthode avec Emmanuel et Raphaël.

https://www.logilab.org/file/6832314/raw

DDD : et si on reprenait l'histoire par le bon bout ? Tout simplement.

La conférence sur le DDD (Domain Driven Design), par Thomas Pierrainet Jérémie Grodziski a été une synthèse intéressante sur une approche de conception et de développement permettant de se concentrer sur la valeur métier. Cette approche permet notamment de communiquer efficacement et de collaborer avec les experts métier (qui doivent être capables de lire et comprendre notre code !). Les conférenciers ont su extraire certains principes et patrons de conception du fameux "blue book" à l'origine de cette expression, et les rendre accessibles : les "values objects", la couche anti-corruption, l'architecture hexagonale, etc.

Forum Ouvert

À cette occasion plus d'une trentaine d'orateurs ont proposé un sujet qui leur tenait à coeur. Les personnes intéressées pouvaient débattre sur chaque sujet en groupes spontanés répartis dans tout l'espace de la conférence pendant 45 minutes.

Dans ce type d'activité, les groupes sont petits et donc la répartition du temps de parole est assez homogène.

Juliette de notre équipe a animé le forum "le bonheur au travail" avec une vingtaine de personnes, elle a pu recueillir beaucoup d'idées intéressantes.

Voici quelques idées qui se sont dégagées de la reflexion :

https://www.logilab.org/file/6831796/raw

Mindfulness & Agile

Dov Tsal Sela nous a présenté "Comprendre tes équipes, pour comprendre à toi-même". La pleine conscience est l'art de regarder le moment présent avec clarté. En nous conseillant de faire un voyage à travers le Taoïsme, les neurosciences et le royaume animal pour comprendre comment sont prises les décisions personnelles et en groupes (et qui les prend…)

Au cours de cet atelier, nous avons pu visionner des vidéos, et même méditer un peu.

Comment j'ai recruté mon pair ?

Juliette a assisté à une conférence animée par Houssam Fakih et Jonathan Salmona centrée sur le recrutement. Partant du principe que les profils trop semblables feront les mêmes erreurs et souhaitant recruter les bonnes personnes pour leur société, ils ont développé leur propre méthode d'évaluation. L'entretien est, pour eux, une des nombreuses vitrines de l'entreprise, aussi souhaitent-ils que cette expérience se déroule de la manière la plus professionnelle possible. Ils ont établi un modèle d'entretien, ce qui assure l'équitabilité des chances pour tous les candidats. Ils ont présenté leur grille d'évaluation, les différentes difficultés rencontrées, les pièges à éviter, les retours de leurs candidats, le suivi des nouvelles recrues ...

Mais aussi...

Laura a participé à une discussion intéressante sur le travail agile réparti sur plusieurs sites. De nombreux participants ont fait des retours sur leur expérience, qui parfois impliquait une équipe de développement répartie dans plusieurs pays. À la distance peuvent donc s'ajouter des difficultés liées aux différences culturelles. En laissant de côté ce dernier aspect qui nous concerne moins à Logilab, plusieurs éléments sont applicables sur nos développements répartis entre Paris et Toulouse :

Des obstacles à garder en tête :

Il est difficile de capter le ressenti à distance.

À distance, on ne bénéficie pas de l'"info café" : ces conversations informelles dans le couloir ou la salle café, qui souvent contiennent des informations utiles pour le projet en cours.

Certaines pratiques sont plus compliquées à distance : rétrospective, planning poker... Mais il existe des applications en ligne pour ça.

Il est important de :

Se rencontrer régulièrement, pour partager la même vision et souder les équipes.

En début de projet, se mettre d'accord sur un "contrat de développement", avec entre autres les bonnes pratiques et le processus de revue et d'intégration.

Que tout le monde ait accès à la même information : idéalement, le product owner devrait être sur un troisième site distant, pour ne "favoriser" personne. Si il n'y a pas de PO, faire en sorte que des développeurs de chaque site puissent assister régulièrement aux réunions client.

Et enfin, pourquoi pas...

Mettre des webcams en salle de pause.

Faire du pair-programming réparti.

blog entry of

Vous êtes passionné(e) d'informatique et souhaitez comprendre et maîtriser le fonctionnement de toute la pile applicative, de la base de données à la feuille de style, pour concevoir et développer des produits avec agilité ?

Nous aussi !

https://www.logilab.org/file/6817931/raw

Consultez notre offre "CDI - développement web (client)" et postulez chez nous !

blog entry of

Recently, I've faced the problem to import the European Union thesaurus, Eurovoc, into cubicweb using the SKOS cube. Eurovoc doesn't follow the SKOS data model and I'll show here how I managed to adapt Eurovoc to fit in SKOS.

This article is in two parts:

  • this is the first part where I introduce what a thesaurus is and what SKOS is,
  • the second part will show how to convert Eurovoc to plain SKOS.

The whole text assumes familiarity with RDF, as describing RDF would require more than a blog entry and is out of scope.

What is a thesaurus ?

A common need in our digital lives is to attach keywords to documents, web pages, pictures, and so on, so that search is easier. For example, you may want to add two keywords:

  • lily,
  • lilium

in a picture's metadata about this flower. If you have a large collection of flower pictures, this will make your life easier when you want to search for a particular species later on.

free-text keywords on a picture

In this example, keywords are free: you can choose whatever keyword you want, very general or very specific. For example you may just use the keyword:

  • flower

if you don't care about species. You are also free to use lowercase or uppercase letters, and to make typos...

free-text keyword on a picture

On the other side, sometimes you have to select keywords from a list. Such a constrained list is called a controlled vocabulary. For instance, a very simple controlled vocabulary with only two keywords is the one about a person's gender:

  • male (or man),
  • female (or woman).
a simple controlled vocabulary

But there are more complex examples: think about how a library organizes books by themes: there are very general themes (eg. Science), then more and more specific ones (eg. Computer science -> Software -> Operating systems). There may also be synonyms (eg. Computing for Computer science) or referrals (eg. there may be a "see also" link between keywords Algebra and Geometry). Such a controlled vocabulary where keywords are organized in a tree structure, and with relations like synonym and referral, is called a thesaurus.

an example thesaurus with a tree of keywords

For the sake of simplicity, in the following we will call thesaurus any controlled vocabulary, even a simple one with two keywords like male/female.

SKOS

SKOS, from the World Wide Web Consortium (W3C), is an ontology for the semantic web describing thesauri. To make it simple, it is a common data model for thesauri that can be used on the web. If you have a thesaurus and publish it on the web using SKOS, then anyone can understand how your thesaurus is organized.

SKOS is very versatile. You can use it to produce very simple thesauri (like male/female) and very complex ones, with a tree of keywords, even in multiple languages.

To cope with this complexity, SKOS data model splits each keyword into two entities: a concept and its labels. For example, the concept of a male person have multiple labels: male and man in English, homme and masculin in French. The concept of a lily flower also has multiple labels: lily in English, lilium in Latin, lys in French.

Among all labels for a given concept, some can be preferred, while others are alternative. There may be only one preferred label per language. In the person's gender example, man may be the preferred label in English and male an alternative one, while in French homme would be the preferred label and masculin and alternative one. In the flower example, lily (resp. lys) is the preferred label in English (resp. French), and lilium is an alternative label in Latin (no preferred label in Latin).

SKOS concepts and labels

And of course, in SKOS, it is possible to say that a concept is broader than another one (just like topic Science is broader than topic Computer science).

So to summarize, in SKOS, a thesaurus is a tree of concepts, and each concept have one or more labels, preferred or alternative. A thesaurus is also called a concept scheme in SKOS.

Also, please note that SKOS data model is slightly more complicated than what we've shown here, but this will be sufficient for our purpose.

RDF URIs defined by SKOS

In order to publish a thesaurus in RDF using SKOS ontology, SKOS introduces the "skos:" namespace associated to the following URI: http://www.w3.org/2004/02/skos/core#.

Within that namespace, SKOS defines some classes and predicates corresponding to what has been described above. For example:

  • the triple (<uri>, rdf:type, skos:ConceptScheme) says that <uri> belongs to class skos:ConceptScheme (that is, is a concept scheme),
  • the triple (<uri>, rdf:type, skos:Concept) says that <uri> belongs to class skos:Concept (that is, is a concept),
  • the triple (<uri>, skos:prefLabel, <literal>) says that <literal> is a preferred label for concept <uri>,
  • the triple (<uri>, skos:altLabel, <literal>) says that <literal> is an alternative label for concept <uri>,
  • the triple (<uri1>, skos:broader, <uri2>) says that concept <uri2> is a broder concept of <uri1>.
blog entry of

This is the second part of an article where I show how to import the Eurovoc thesaurus from the European Union into an application using a plain SKOS data model. I've recently faced the problem of importing Eurovoc into CubicWeb using the SKOS cube, and the solution I've chose is discussed here.

The first part was an introduction to thesauri and SKOS.

The whole article assumes familiarity with RDF, as describing RDF would require more than a blog entry and is out of scope.

Difficulties with Eurovoc and SKOS

Eurovoc

Eurovoc is the main thesaurus covering European Union business domains. It is published and maintained by the EU commission. It is quite complex and big, structured as a tree of keywords.

You can see Eurovoc keywords and browse the tree from the Eurovoc homepage using the link Browse the subject-oriented version.

For example, when publishing statistics about education in the EU, you can tag the published data with the broadest keyword Education and communications. Or you can be more precise and use the following narrower keywords, in increasing order of preference: Education, Education policy, Education statistics.

Problem: hierarchy of thesauri

The EU commission uses SKOS to publish its Eurovoc thesaurus, so it should be straightforward to import Eurovoc into our own application. But things are not that simple...

For some reasons, Eurovoc uses a hierarchy of concept schemes. For example, Education and communications is a sub-concept scheme of Eurovoc (it is called a domain), and Education is a sub-concept scheme of Education and communications (it is called a micro-thesaurus). Education policy is (a label of) the first concept in this hierarchy.

But with SKOS this is not possible: a concept scheme cannot be contained into another concept scheme.

Possible solutions

So to import Eurovoc into our SKOS application, and not loose data, one solution is to turn sub-concept schemes into concepts. We have two strategies:

  • keep only one concept scheme (Eurovoc) and turn domains and micro-thesauri into concepts,
  • keep domains as concept schemes, drop Eurovoc concept scheme, and only turn micro-thesauri into concepts.

Here we will discuss the latter solution.

Lets get to work

Eurovoc thesaurus can be downloaded at the following URL: http://publications.europa.eu/mdr/resource/thesaurus/eurovoc/skos/eurovoc_skos.zip

The ZIP archive contains only one XML file named eurovoc_skos.rdf. Put it somewhere where you can find it easily.

To read this file easily, we will use the RDFLib Python library. This library makes it really convenient to work with RDF data. It has only one drawback: it is very slow. Reading the whole Eurovoc thesaurus with it takes a very long time. Make the process faster is the first thing to consider for later improvements.

Reading the Eurovoc thesaurus is as simple as creating an empty RDF Graph and parsing the file. As said above, this takes a long long time (from half an hour to two hours).

import rdflib

eurovoc_graph = rdflib.Graph()
eurovoc_graph.parse('<path/to/eurovoc_skos.rdf>', format='xml')
<Graph identifier=N52834ca3766d4e71b5e08d50788c5a13 (<class 'rdflib.graph.Graph'>)>

We can see that Eurovoc contains more than 2 million triples.

len(eurovoc_graph)
2828910

Now, before actually converting Eurovoc to plain SKOS, lets introduce some helper functions:

  • the first one, uriref(), will allow us to build RDFLib URIRef objects from simple prefixed URIs like skos:prefLabel or dcterms:title,
  • the second one, capitalized_eurovoc_domains(), is used to convert Eurovoc domain names, all uppercase (eg. 32 EDUCATION ET COMMUNICATION) to a string where only first letter is uppercase (eg. 32 Education and communication)
import re

from rdflib import Literal, Namespace, RDF, URIRef
from rdflib.namespace import DCTERMS, SKOS

eu_ns = Namespace('http://eurovoc.europa.eu/schema#')
thes_ns = Namespace('http://purl.org/iso25964/skos-thes#')

prefixes = {
    'dcterms': DCTERMS,
    'skos': SKOS,
    'eu': eu_ns,
    'thes': thes_ns,
}

def uriref(prefixed_uri):
    prefix, value = prefixed_uri.split(':', 1)
    ns = prefixes[prefix]
    return ns[value]

def capitalized_eurovoc_domain(domain):
    """Return the given Eurovoc domain name with only the first letter uppercase."""
    return re.sub(r'^(\d+\s)(.)(.+)$',
                  lambda m: u'{0}{1}{2}'.format(m.group(1), m.group(2).upper(), m.group(3).lower()),
                  domain, re.UNICODE)

Now the actual work. After using variables to reference URIs, the loop will parse each triple in original graph and:

  • discard it if it contains deprecated data,
  • if triple is like (<uri>, rdf:type, eu:Domain), replace it with (<uri>, rdf:type, skos:ConceptScheme),
  • if triple is like (<uri>, rdf:type, eu:MicroThesaurus), replace it with (<uri>, rdf:type, skos:Concept) and add triple (<uri>, skos:inScheme, <domain_uri>),
  • if triple is like (<uri>, rdf:type, eu:ThesaurusConcept), replace it with (<uri>, rdf:type, skos:Concept),
  • if triple is like (<uri>, skos:topConceptOf, <microthes_uri>), replace it with (<uri>, skos:broader, <microthes_uri>),
  • if triple is like (<uri>, skos:inScheme, <microthes_uri>), replace it with (<uri>, skos:inScheme, <domain_uri>),
  • keep triples like (<uri>, skos:prefLabel, <label_uri>), (<uri>, skos:altLabel, <label_uri>), and (<uri>, skos:broader, <concept_uri>),
  • discard all other non-deprecated triples.

Note that, to replace a micro thesaurus with a domain, we have to build a mapping between each micro thesaurus and its containing domain (microthes2domain dict).

This loop is also quite long.

eurovoc_ref = URIRef(u'http://eurovoc.europa.eu/100141')
deprecated_ref = URIRef(u'http://publications.europa.eu/resource/authority/status/deprecated')
title_ref = uriref('dcterms:title')
status_ref = uriref('thes:status')
class_domain_ref = uriref('eu:Domain')
rel_domain_ref = uriref('eu:domain')
microthes_ref = uriref('eu:MicroThesaurus')
thesconcept_ref = uriref('eu:ThesaurusConcept')
concept_scheme_ref = uriref('skos:ConceptScheme')
concept_ref = uriref('skos:Concept')
pref_label_ref = uriref('skos:prefLabel')
alt_label_ref = uriref('skos:altLabel')
in_scheme_ref = uriref('skos:inScheme')
broader_ref = uriref('skos:broader')
top_concept_ref = uriref('skos:topConceptOf')

microthes2domain = dict((mt, next(eurovoc_graph.objects(mt, uriref('eu:domain'))))
                        for mt in eurovoc_graph.subjects(RDF.type, uriref('eu:MicroThesaurus')))

new_graph = rdflib.ConjunctiveGraph()
for subj_ref, pred_ref, obj_ref in eurovoc_graph:
    if deprecated_ref in list(eurovoc_graph.objects(subj_ref, status_ref)):
        continue
    # Convert eu:Domain into a skos:ConceptScheme
    if obj_ref == class_domain_ref:
        new_graph.add((subj_ref, RDF.type, concept_scheme_ref))
        for title in eurovoc_graph.objects(subj_ref, pref_label_ref):
            if title.language == u'en':
                new_graph.add((subj_ref, title_ref,
                               Literal(capitalized_eurovoc_domain(title))))
                break
    # Convert eu:MicroThesaurus into a skos:Concept
    elif obj_ref == microthes_ref:
        new_graph.add((subj_ref, RDF.type, concept_ref))
        scheme_ref = next(eurovoc_graph.objects(subj_ref, rel_domain_ref))
        new_graph.add((subj_ref, in_scheme_ref, scheme_ref))
    # Convert eu:ThesaurusConcept into a skos:Concept
    elif obj_ref == thesconcept_ref:
        new_graph.add((subj_ref, RDF.type, concept_ref))
    # Replace <concept> topConceptOf <MicroThesaurus> by <concept> broader <MicroThesaurus>
    elif pred_ref == top_concept_ref:
        new_graph.add((subj_ref, broader_ref, obj_ref))
    # Replace <concept> skos:inScheme <MicroThes> by <concept> skos:inScheme <Domain>
    elif pred_ref == in_scheme_ref and obj_ref in microthes2domain:
        new_graph.add((subj_ref, in_scheme_ref, microthes2domain[obj_ref]))
    # Keep label triples
    elif (subj_ref != eurovoc_ref and obj_ref != eurovoc_ref
          and pred_ref in (pref_label_ref, alt_label_ref)):
        new_graph.add((subj_ref, pred_ref, obj_ref))
    # Keep existing skos:broader relations and existing concepts
    elif pred_ref == broader_ref or obj_ref == concept_ref:
        new_graph.add((subj_ref, pred_ref, obj_ref))

We can check that we now have far less triples than before.

len(new_graph)
388582

Now we dump this new graph to disk. We choose the Turtle format as it is far more readable than RDF/XML for humans, and slightly faster to parse for machines. This file will contain plain SKOS data that can be directly imported into any application able to read SKOS.

with open('eurovoc.n3', 'w') as f:
    new_graph.serialize(f, format='n3')

With CubicWeb using the SKOS cube, it is a one command step:

cubicweb-ctl skos-import --cw-store=massive <instance_name> eurovoc.n3
blog entry of

At the core of the Logilab infrastructure is a highly-available pair of small machines dedicated to our main directory and authentication services: LDAP, DNS, DHCP, Kerberos and Radius.

The machines are small fanless boxes powered by a 1GHz Via Eden processor, 512Mb of RAM and 2Gb of storage on a CompactFlash module.

They have served us well for many years, but now is the time for an improvement. We've bought a pair of Lanner FW-7543B that have the same form-factor. They are not fanless, but are much more powerful. They are pretty nice, but have one major drawback: their firmware does not boot on a legacy BIOS-mode device when set up in UEFI. Another hard point is that they do not have a video connector (there is a VGA output on the motherboard, but the connector is optional), so everything must be done via the serial console.

https://www.logilab.org/file/6679313/raw/FW-7543_front.jpg

I knew the Debian Jessie installer would provide everything that is required to handle an UEFI-based system, but it took me a few tries to get it to boot.

First, I tried the standard netboot image, but the firmware did not want to boot from a USB stick, probably because the image requires a MBR-based bootloader.

Then I tried to boot from the Refind bootable image and it worked! At least I had the proof this little beast could boot in UEFI. But, although it is probably possible, I could not figure out how to tweak the Refind config file to make it boot properly the Debian installer kernel and initrd.

https://www.logilab.org/file/6679257/raw/uefi_lanner_nope.png

Finally I gave a try to something I know much better: Grub. Here is what I did to have a working UEFI Debian installer on a USB key.

Partitionning

First, in the UEFI world, you need a GPT partition table with a FAT partition typed "EFI System":

david@laptop:~$ sudo fdisk /dev/sdb
Welcome to fdisk (util-linux 2.25.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): g
Created a new GPT disklabel (GUID: 52FFD2F9-45D6-40A5-8E00-B35B28D6C33D).

Command (m for help): n
Partition number (1-128, default 1): 1
First sector (2048-3915742, default 2048): 2048
Last sector, +sectors or +size{K,M,G,T,P} (2048-3915742, default 3915742):  +100M

Created a new partition 1 of type 'Linux filesystem' and of size 100 MiB.

Command (m for help): t
Selected partition 1
Partition type (type L to list all types): 1
Changed type of partition 'Linux filesystem' to 'EFI System'.

Command (m for help): p
Disk /dev/sdb: 1.9 GiB, 2004877312 bytes, 3915776 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 52FFD2F9-45D6-40A5-8E00-B35B28D6C33D

Device     Start    End Sectors  Size Type
/dev/sdb1   2048 206847  204800  100M EFI System

Command (m for help): w

Install Grub

Now we need to install a grub-efi bootloader in this partition:

david@laptop:~$ pmount sdb1
david@laptop:~$ sudo grub-install --target x86_64-efi --efi-directory /media/sdb1/ --removable --boot-directory=/media/sdb1/boot
Installing for x86_64-efi platform.
Installation finished. No error reported.

Copy the Debian Installer

Our next step is to copy the Debian's netboot kernel and initrd on the USB key:

david@laptop:~$ mkdir /media/sdb1/EFI/debian
david@laptop:~$ wget -O /media/sdb1/EFI/debian/linux http://ftp.fr.debian.org/debian/dists/jessie/main/installer-amd64/current/images/netboot/debian-installer/amd64/linux
--2016-06-13 18:40:02--  http://ftp.fr.debian.org/debian/dists/jessie/main/installer-amd64/current  /images/netboot/debian-installer/amd64/linux
Resolving ftp.fr.debian.org (ftp.fr.debian.org)... 212.27.32.66, 2a01:e0c:1:1598::2
Connecting to ftp.fr.debian.org (ftp.fr.debian.org)|212.27.32.66|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3120416 (3.0M) [text/plain]
Saving to: ‘/media/sdb1/EFI/debian/linux’

/media/sdb1/EFI/debian/linux      100%[========================================================>]   2.98M      464KB/s   in 6.6s

2016-06-13 18:40:09 (459 KB/s) - ‘/media/sdb1/EFI/debian/linux’ saved [3120416/3120416]

david@laptop:~$ wget -O /media/sdb1/EFI/debian/initrd.gz http://ftp.fr.debian.org/debian/dists/jessie/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz
--2016-06-13 18:41:30--  http://ftp.fr.debian.org/debian/dists/jessie/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz
Resolving ftp.fr.debian.org (ftp.fr.debian.org)... 212.27.32.66, 2a01:e0c:1:1598::2
Connecting to ftp.fr.debian.org (ftp.fr.debian.org)|212.27.32.66|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 15119287 (14M) [application/x-gzip]
Saving to: ‘/media/sdb1/EFI/debian/initrd.gz’

/media/sdb1/EFI/debian/initrd.g    100%[========================================================>]  14.42M    484KB/s   in 31s

2016-06-13 18:42:02 (471 KB/s) - ‘/media/sdb1/EFI/debian/initrd.gz’ saved [15119287/15119287]

Configure Grub

Then, we must write a decent grub.cfg file to load these:

david@laptop:~$ echo >/media/sdb1/boot/grub/grub.cfg <<EOF
menuentry "Jessie Installer" {
  insmod part_msdos
  insmod ext2
  insmod part_gpt
  insmod fat
  insmod gzio
  echo  'Loading Linux kernel'
  linux /EFI/debian/linux --- console=ttyS0,115200
  echo 'Loading InitRD'
  initrd /EFI/debian/initrd.gz
}
EOF

Et voilà, piece of cake!

blog entry of

https://www.logilab.org/file/5920040/raw/gite.jpeg

J'ai à mon tour eu l'opportunité de participer au raid agile #5 organisé par Pablo Pernot et Claude Aubry dans les Cévennes le mois dernier, et j'en suis revenue ravie. Ces quelques jours en immersion dans un cadre magnifique permettent de se déconnecter du quotidien pour se concentrer pleinement sur l'agilité, le tout dans une ambiance chaleureuse. La formation est dense, mais elle est orientée pratique, prévoit des pauses et fait la part belle aux échanges, conseils et retours d'expérience, ce qui fait qu'on ne voit pas le temps passer. J'y ai appris beaucoup de choses, principalement sur des outils de définition d'un produit que nous utilisons peu (pas assez ?) chez Logilab.

J'ai donc apprécié tout particulièrement les ateliers sur la définition des parties prenantes, des personas (et dans la même idée, je vais me renseigner sur l'empathy mapping). J'ai également pu découvrir l'impact mapping, le petit plus étant de le précéder d'une étude d'impacts rétro-futuriste, qui permet d'identifier les vrais besoins : on se projette dans un futur où le produit connaît un succès total, en se mettant dans la peau de la ou des persona(s) principale(s). On se "souvient" des raisons (orientées comportement) qui font qu'on ne pourrait plus se passer du produit: les impacts.

En mars 2015, Sylvain Thénault était revenu du raid agile avec ces réflexions. Un an après, je profite de l'occasion pour faire un point sur la façon dont nos pratiques ont évolué, et les choses sur lesquelles j'aimerais avancer suite à mon expérience du raid.

En un an, nous avons progressé sur pas mal d'aspects à Toulouse :

  • Nous travaillons plus en équipe, chaque individu étant maintenant apte à intervenir sur davantage de projets différents. C'est extrêmement positif, puisque cela permet mieux de gérer la charge, surtout dans un contexte où nous sommes peu nombreux et intervenons sur beaucoup de projets petits à moyens, avec des dates butoir parfois serrées et des congés à gérer. Une montée en compétence a été réalisée au passage, et j'ai l'impression que globalement l'esprit d'équipe et la communication en ont été renforcés.
  • Après pas mal de réflexion et d'évolutions, notre "kanban" est maintenant un peu plus stable (il ressemble à un kanban mais n'en est pas vraiment un, car pas de limites ni de flux tiré). Nous utilisons les colonnes classiques "backlog", "ready", "doing", "review", et une ligne par projet en cours. Sur chaque post-it contenant une tâche, on ajoute une pastille de couleur représentant la personne en charge de sa réalisation. Un coup d’œil rapide permet donc de visualiser les projets sur lesquels il y a trop de travail en attente, en cours ou à intégrer, ainsi que les sous-charges ou surcharges des membres de l'équipe.
  • Toujours dans la catégorie du management visuel: nous avons maintenant un tableau d'obstacles, ainsi qu'un "tableau des contraintes" (mis à jour une fois par semaine) permettant de résumer par projet les risques, dates butoir, charge restante, absence de membres de l'équipe de développement, etc. Ce tableau facilite l'affectation des membres de l'équipe sur les différents projets et le respect des délais.
  • Nous avons effectué deux delegation board à un an d'intervalle. Cela nous a permis d'identifier des éléments clé dans notre travail au quotidien, de clarifier les rôles les concernant et de soulever des problèmes liés. On a pu noter des améliorations lors de la seconde session, par rapport à la première (principalement au niveau organisationnel).
  • Nous essayons de faire une rétrospective tous les mois. Celles que nous avons faites nous ont permis d'améliorer nos pratiques (nous avons notamment progressé sur nos réunions debout, et sur des notions de qualité via la rédaction commune d'un working agreement)

En revanche, nous avons un nouveau fauteuil que personne n'utilise (peut-être ressemble-t-il trop à une chaise de bureau, me souffle-t-on dans l'oreillette) ! La question du rythme soutenable mentionnée par Sylvain ne semble pas être la préocupation principale, peut-être parce que malgré la charge fluctuante liée au contexte du travail dans une petite société de service, nous n'atteignons généralement pas un rythme insoutenable.

Au cours de l'année à venir, je pense que nous pourrions travailler entre autres sur les points suivants :

  • Continuer à avancer sur le travail en équipe sur les projets, qui mérite d'être encore amélioré.
  • Travailler sur les rétrospectives : faire tourner le facilitateur, essayer de précéder la rétrospective d'un exercice d'"échauffement" pour se mettre dans le bain. Je suis revenue du raid avec de nouvelles idées de format, à tester !
  • Un atelier réalisé au cours du raid consistait à classer une grande quantité de pratiques agiles dans différentes catégories: en cours d'acquisition, acquis, non acquis, non souhaité. Réaliser cet exercice avec l'ensemble de l'équipe amènerait sûrement des discussions intéressantes. Cela permettrait en outre de partager une vision commune, et de repérer les points à améliorer en priorité dans nos pratiques.
  • Si l'occasion se présente, j'aimerais que nous essayions la cotation de l'extrême, découverte au cours du raid, efficace pour estimer grossièrement un gros backlog en peu de temps.
  • Utiliser dès que possible une partie des outils de définition de produit vus au cours du raid
blog entry of