[ascendances] Cohabitation Jinja et AngularJS
[logilab] Pylint development moving to BitBucket
Hi everyone,
After 10 years of hosting Pylint on our own forge at logilab.org, we've decided to publish version 1.0 and move Pylint and astng development to BitBucket. There has been repository mirrors there for some time, but we intend now to use all BitBucket features, notably Pull Request, to handle various development tasks.
There are several reasons behind this. First, using both BitBucket and our own forge is rather cumbersome, for integrators at least. This is mainly because BitBucket doesn't provide support for Mercurial's changeset evolution feature while our forge relies on it. Second, our forge has several usability drawbacks that make it hard to use for newcomers, and we lack the time to be responsive on this. Finally, we think that our quality-control process, as exposed by our forge, is a bit heavy for such community projects and may keep potential contributors away.
All in all, we hope this will help to have a wider contributor audience as well as more regular maintainers / integrators which are not Logilab employees. And so, bring the best Pylint possible to the Python community!
Logilab.org web pages will be updated to mention this, but kept as there is still valuable information there (eg tickets). We may also keep automatic tests and package building services there.
So, please use https://bitbucket.org/logilab/pylint as main web site regarding pylint development. Bug reports, feature requests as well as contributions should be done there. The same move will be done for Pylint's underlying library, logilab-astng (https://bitbucket.org/logilab/astng). We also wish in this process to move it out of the 'logilab' python package. It may be a good time to give it another name, if you have any idea don't hesitate to express yourself.
Last but not least, remember that Pylint home page may be edited using Mercurial, and that the new http://docs.pylint.org is generated using the content found in Pylint source doc subdirectory.
Pylint turning 10 and moving out of its parents is probably a good time to thank Logilab for paying me and some colleagues to create and maintain this project!
[afpyro] AFPyro à Lyon - le 24 avril 2013
Un Afpyro aura lieu le mercredi 24 avril à partir de 20h à l’Antre Autre - 11 rue Terme - 69001 Lyon.
Blandine Bourgois (du groupe Nao sur Seine) et Xavier Basset (du groupe Nao is a gone) nous feront une présentation sur et avec un robot Nao, un petit robot humanoïde programmé en Python.
L’Antre Autre est un lieu où nous pouvons discuter autour d’un verre, et, pour ceux qui le souhaitent, prendre un repas.
- Pour se rendre à l’Antre Autre :
- en métro : arrêt Hôtel de Ville
- en bus : lignes C13 et C18 arrêt Mairie du 1er ou lignes 19, C14 et C3 à l’arrêt Terreaux
- en vélo’v : stations Place Sathonay, Carmélites Burdeau, Place de la paix
[novapost] Django1.5 : passer au Configurable User Model
Depuis la version 1.5 de Django, il est possible d'utiliser un Configurable User Model en lieu et place de django.contrib.auth.User.
Cela permet, par exemple, de se passer de proxy model ou encore de fusionner le profil avec l'utilisateur, pour éviter des join dans les requêtes SQL.
Très pratique, et facile à mettre en place sur un projet qui commence juste, mais comment gérer ça en utilisant South sur un projet déjà bien en place ?
Le but est donc de fusionner l'utilisateur et le profil, avec pour aide/contrainte d'utiliser South, autant sur des plateformes existantes (serveur de production, de pré-production) que sur les plateformes de développement : donc les migrations doivent fonctionner sur une création de base, tout autant que sur une migration simple.
Nous allons détailler plusieurs stratégies.
Contexte
Notre projet utilise depuis longtemps un proxy model sur l'utilisateur, ne rajoutant que quelques méthodes. Toutes les données liées à l'utilisateur sont par ailleurs stockées dans un profil, qui est utilisé par le biais de get_profile() (et le setting AUTH_PROFILE_MODULE).
from django.db import models
from django.contrib.auth.models import User
class RH2User(User):
class Meta:
proxy = True
...
class RH2UserProfile(models.Model):
some_field = models.CharField(max_length=50)
some_other_field = models.BooleanField()
def some_method(self):
...
Le résultat, une fois le profil fusionné avec l'utilisateur :
from django.db import models
from django.contrib.auth.models import AbstractUser
class RH2User(AbstractUser):
some_field = models.CharField(max_length=50)
some_other_field = models.BooleanField()
def some_method(self):
...
Ne pas oublier de fusionner les managers, les méthodes save(), et de dédoublonner les champs ayant le même nom (dans notre cas, RH2UserProfile.last_login a été renommé en RH2User.previous_last_login, étant donné que le modèle auth.User d'origine avait déjà un champ last_login).
Il faut par ailleurs rechercher et remplacer le cas échéant toutes les occurrences de :
- RH2UserProfile
- get_profile()
- rh2userprofile__
- .user
La problématique
À partir du moment où le paramètre AUTH_USER_MODEL est renseigné :
- les tables auth_user, auth_user_user_permissions, auth_user_groups ne sont plus automatiquement crées par un python manage.py syncdb
- toutes les migrations South existantes sur des modèles ayant une ForeignKey ou Many to Many ne passerons plus tel quel
Il y a donc principalement deux stratégies pour les migrations South, une fois qu'on a notre modèle RH2User complet (et non plus proxy) ainsi que AUTH_USER_MODEL = 'account.RH2User' dans les paramètres :
- Modifier la migration initiale de l'app account, puis toutes les migrations suivantes ainsi que les migrations des app ayant une relation avec l'utilisateur pour qu'elles se basent sur account_rh2user au lieu de auth_user
- Rajouter la création de la table auth_user, auth_user_user_permissions et auth_user_groups dans la migration initiale de l'app contenant le modèle complet, puis rajouter une migration qui va renommer la table auth_user en account_rh2user
Dans les deux cas, il faudra être attentif à l'ordre d'exécution des migrations : toutes les applications ayant une relation avec l'utilisateur devront dépendre de la migration initiale qui crée la table auth_user ou account_rh2user.
Dans le deuxième cas, il faudra de plus que la première des migration suivant le renommage, pour chaque application, dépende de cette migration.
Création de account_rh2user et modification des migrations
Le plus simple est de créer une migration de schéma pour avoir le code nécessaire à la migration 0001_initial de l'application account :
$ python manage.py schemamigration account
Il suffit alors de recopier le code de la migration créée, de le rajouter au fichier account/migrations/0001_initial.py, puis de supprimer cette nouvelle migration qui ne sera pas utilisée.
Il faut ensuite modifier chacune des migration, en prenant exemple sur ce qui a été fait sur django-oauth2-provider.
Il reste la problématique de la migration des serveurs déjà en production (qui ont déjà un certain nombre de migrations effectuées, et une base de donnée à conserver). Une solution serait de créer une migration de données et de tester l'existence de la table auth_user, et le cas échéant de dupliquer les données dans la table account_rh2user.
N'ayant pas testé cette solution, je ne peux la garantir.
Création de auth_user puis renommage
C'est la solution que nous avons choisi, étant donné le nombre de migrations que nous avons (près d'une centaine), qu'il aurait fallu modifier une à une, ainsi que le soucis de migration des serveurs déjà en production.
Il faut dans l'ordre :
- créer les tables auth_user, auth_user_user_permissions et auth_user_groups dans la migration 0001_initial de account
- créer une migration dans account qui renomme la table auth_user en account_rh2user
- créer une migration dans account qui rajoute les champs du modèle profil à l'utilisateur
- créer une migration de données pour dupliquer toutes les données de profil dans la table account_rh2user
- pour chaque application ayant une relation vers l'utilisateur, la prochaine migration créée devra dépendre de la migration qui renomme la table
Conclusion
Le plus compliqué dans toute cette histoire est la gestion de dépendances entre les migrations.
Une autre solution non évoquée aurait été de repartir de 0 pour les migrations : supprimer toutes les migrations existantes, ainsi que la table south_migrationhistory, puis reconvertir toutes les applications à South :
$ python manage.py convert_to_south ....
L'avantage est qu'il n'y a alors aucun soucis de dépendances entre les migrations, et qu'on repars de quelque chose de propre.
Les inconvénients sont multiples : gérer une migration (à la main?) pour les plateformes en cours d'utilisation, impossibilité de retourner en arrière automatiquement, perte de l'historique...
Il y a une autre possibilité (à tester !) qui consiste à spécifier l'attribut db_table = 'auth_user' dans la Meta de notre nouveau modèle RH2User, pour qu'il utilise exactement la même table. En théorie, il n'y a alors pas besoin de migration, mais il reste à gérer la fusion du profil dans l'utilisateur.
[logilab] Pylint 10th years anniversary from June 17 to 19 in Toulouse
After a quick survey, we're officially scheduling Pylint 10th years anniversary sprint from monday, June 17 to wednesday, June 19 in Logilab's Toulouse office.
There is still some room available if more people want to come, drop me a note (sylvain dot thenault at logilab dot fr).
[Biologeek] Passage à l'échelle
Je m'interroge de plus en plus sur cette notion de passage à l'échelle que l'on nous encourage à anticiper avec le Cloud. J'ai de plus en plus l'impression qu'elle est liée à des business models déficients qui misent sur la masse d'utilisateurs pour avoir une chance à terme de monétiser le service. Je comprends que l'on puisse avoir des investissements à rentabiliser et qu'en visant haut, malléable, on pense pouvoir retrouver un équilibre financier plus rapidement. Pourquoi placer des paliers financiers aussi élevés ?
Avoir l'ambition d'un service mondial avec des millions d'utilisateurs est finalement aller à l'encontre du Web en centralisant des données et des usages. Il y a de la place pour plusieurs services, pour de la diversité, pour des motivations et des valeurs différentes, pour des communautés complémentaires. Pourquoi vouloir devenir le TF1 du Web ?
Quelles sont les relations que vous pouvez entretenir avec des millions d'usagers ? Sont-elles sincères ? Automatisées ? Avez-vous délégué ces relations ? La reconnaissance de l'utilité d'un service passe par ces retours, c'est une forme de motivation qui s'inscrit dans la durée. On ne vend pas un service au plus offrant lorsque l'on a établi ces relations. Pourquoi chercher à ne plus être en mesure de gérer ces liens ?
Le passage à l'échelle est emblématique d'une croissance effrénée et malsaine.
[afpyro] AFPyro à Bruxelles (BE) - vendredi 10 Mai
Un AFPyro, Apéro Python, aura lieu ce 10 mai à l’Université de Bruxelles. Il se composera d’une série de Lightning Talks au Batiment K de l’ULB suivi d’un passage au Restaurant « La Bécasse ».
Le thème est « les projets Python libres que vous faites » pour avoir une idée de ce qui se fait en Belgique. Bien entendu, ce thème n’est pas limitant et si vous souhaitez parler d’autre chose, faites vous plaisir ! La deadline pour les propositions de Lightning Talk est le dimanche 28 au soir, vous pouvez me les envoyer directement à l’adresse : cortex@worlddomination.be
Si vous souhaitez venir au restaurant après, inscrivez-vous sur le Framadate pour que l’on puisse réserver le bon nombre de place: http://framadate.org/jze5k7xeqgo3dlh5
Pour récapituler :
- Début des lightning talks à 19h au bâtiment K de l’ULB (Campus Solbosh), salle K.4.601
- restaurant à « La Bécasse » à partir de 21h00
Au plaisir de vous y voir.
[Biologeek] Des API et des hommes
Les API actuelles — s'auto-proclamant RESTful — nécessitent bien souvent de développer un client qui leur est propre pour accéder aux données en raison de leurs spécificités. Au mieux, ces API utilisent HTTP à bon escient et font transiter du JSON à partir d'URL « propres ».
Cela semble bien éloigné de la vision de Roy T. Fielding (qui a défini REST en 2000 dans sa thèse) et qui a écrit un billet on ne peux plus clair en 2008 :
A truly RESTful API looks like hypertext. Every addressable unit of information carries an address.
Puis surenchérit en commentaire :
Think of it in terms of the Web. How many Web browsers are aware of the distinction between an online-banking resource and a wiki resource? None of them.
5 ans plus tard, on en est encore à réécrire un client pour chaque API ce qui équivaudrait à écrire un navigateur propre à chaque site web visité ! Comment y remédier facilement ? Revenir à la partie oubliée de REST : les liens.
Si votre API devient navigable, en liant chaque ressource présentée depuis sa racine, un client générique va pouvoir la parcourir de proche en proche en suivant les liens comme un utilisateur le fait sur le Web.
Cela résout énormément de problématiques à la fois lorsque l'on prend cette approche :
- versionnement : est-ce qu'un utilisateur se soucie de la version du site qu'il consulte ? Non. Il suit les liens et si la migration a bien été effectuée il y a des redirections et les codes HTTP appropriées pour gérer ses anciens favoris. Les formulaires ont été mis à jour avec le site et il suffit qu'il remplisse correctement ceux qui lui sont dorénavant présentés.
- URL propres : est-ce qu'un utilisateur se soucie de la beauté des URL qu'il parcoure ? Quand je vois la tête de celles produites par Google ou Amazon j'en doute. Un développeur ne devrait pas avoir à se soucier de cela si le client suit les liens qui lui sont proposés.
- documentation : est-ce qu'un utilisateur a besoin d'une documentation pour naviguer sur votre site ? De toute façon, il y a peu de chance qu'il la lise, en revanche il est utile de lui formuler des messages d'erreurs intelligibles lorsqu'il se trompe de chemin. Il peut être intéressant de faire un rappel sur le métier et les concepts abordés car le développeur — à la différence du visiteur — n'est peut-être pas concerné par le sujet de l'API en question.
- pagination : en utilisant les attributs permettant de typer la relation entre les liens, il est possible de fournir les liens vers la page suivante et précédente explicitement.
Ces questions se sont posées pour les sites Web également il y a des années : souvenez-vous des sites avec un /v4/ dans l'URL ou d'une page d'accueil expliquant comment accéder aux différentes parties du site en « cliquant ici ».
Bien sûr tout cela implique d'avoir un format qui soit hypertexte (pas JSON donc) comme XHTML ou Atom. Si vous voulez vraiment adapter votre JSON actuel il existe 4 implémentations tentant d'introduire des liens typés :
- JSON-LD (LD pour Linked Data), proche des concepts du Web Sémantique ;
- HAL JSON le plus simple, peut-être un peu trop ;
- JSON Collections que je n'ai pas essayé ;
- Siren le plus récent qui est en train de monter rapidement.
Lorsque vous voulez fournir un moyen d'accéder à vos données via une API hypermedia, mettez vous à la place du développeur et demandez vous si votre API est navigable, fait partie intégrante du Web et nécessite une documentation.
Ce billet fait suite à mon intervention à Mix-IT lors d'un lightning talk dont vous pourrez retrouver le support sur la partie dédiée.
[tarek] Thoughts on Load Testing
We are Funkload fans at Mozilla Services. Writing a load test against one of our web service is dead easy using that tool.
It boils down to writing a functional test in Python, that calls the service, then ask Funkload to run it in parallel, accross many threads and boxes.
We've written a web app on the top of Funkload called Marteau - initial blog post about it that now allows us to provision slaves on demand on Amazon WS and run distributed load tests.
You can watch this hangout to better understand what Marteau & Funkload are https://plus.google.com/106436370949746015255/posts/Lq7t4jkiwNR
Marteau is already useful to us but I am now facing some difficulties to improve the tool and add some features we want, like:
- realtime JS charts of the ongoing load test
- web socket load testing
- the ability to run load tests written in other languages or frameworks, like node.js
Funkload is a project that was started 10 years ago, and is very robust. But it has evolved from a tool that runs on a single box to a distributed load testing tool by using SSH. In other words, a distributed load test in Funkload is just several load tests running in parallel, then a merge of all the results files that are copied back to a single box through SSH.
It means that there is no specific client/server protocol and no way to interact live with the slaves that are running in the distributed mode. You can kill them of course, or just wait for them to return.
I started to add some communication channel in the tool but the core itself was not built with this in mind.
I have found the locust.io project, which has this approach and which looks very promising, but does not exactly provide what I am looking for.
For example, I don't really want developers to have to write load tests using yet another set of APIs. The concept of writing Python unit tests is fabulous and I don't want to lose it.
Starting Loads
I am experimenting on something new, based on what we've learned from our experience with Funkload and what we need in Marteau.
It's called Loads and it's a client/server architecture based on ZMQ that will use a very simple protocol based on Message Pack or maybe BJson - we will see.
It's quite similar to locust.io in the principles, but instead of introducing new APIS, it's going to rely on a set of API people know & like : Requests.
So, how will a load test with Loads look like ?
import unittest
from loads import Session
class TestWebSite(unittest.TestCase):
def setUp(self):
self.session = Session()
def test_something(self):
res = self.session.get('http://blog.ziade.org')
self.assertTrue('Tarek' in res.content)
That's it. A unittest class that uses a Session class. The Session object is the same one you find in Requests.
I am not sure yet how I will extend the API so it can work with web sockets. That'll come later.
This test can then be executed using the loads command. Example for 10 concurrent users and 10 runs each:
$ bin/loads test_blog.TestWebSite.test_something -c 10 -n 10
[====================================================================================================] 100%
Like locust.io, Loads uses greenlets here, so you can already push quite some load from a single box.
Everytime a request is done, the status code returned by the server and the time it took are pushed in a stream. The stream can be the standard output or a ZMQ stream.
And I will be using the ZMQ stream to actually drive distributed tests.
Each agent will connect to a master through ZMQ. The master will be able to drive them through a dedicated ROUTE socket and will ask agents to run some load tests.
Results will be sent back via ZMQ in real-time using a dedicated channel.
The master will then publish all results in a merged stream - a ZMQ pub socket.
From there, the Marteau web app will be able to register to that stream of result to display some real time charts and allow any kind of interaction. Or any app that whishes to do something with the results.
And since there are some ZMQ bindings in most languages, it's possible to implement a node.js client for example, so the system can have agents able to run Javascript-based tests and report back results to the master like the built-in Python agent.
That's the plan. I have started the prototype here: https://github.com/tarekziade/loads and I am very excited about this project.
[afpyro] AFPyro à Toulouse le 30 avril 2013
Un Afpyro aura lieu le mardi 30 avril à partir de 20h à La Tireuse 24 rue Pargaminières
Pas de présentation prévue, car organisé au pied levé à la pause café ! Ce sera juste une rencontre autour d’une bière (ou d’un jus de fruit) pour parler de python, avec des pythonistas toulousains.
Pour se rendre à la tireuse, métro ligne A : arrêt Capitole, puis aller dans la rue qui rejoint la place St Pierre.
[Inspyration] Quelques modules intéressants
[sciunto] Des logiciels pour être efficace
[ascendances] Évolution du nombre de messages sur debian-l10n-fr et debian-users-fr avec pychart
[afpy.org] Le nouveau site de l'AFPy
[tarek] Fun with AngularJS & Cornice
I blogged about it a few weeks ago: in my opinion, we're moving to an ecosystem where our web applications are built of JSON web services and of Javascript applications that use those services to fetch data and display them.
In other words, server-side templating is fading away and client-side templating based on frameworks like AngularJs is getting a lot of traction.
As a Python developer in the Mozilla Services team, one tool that is starting to play an important role is Cornice, because it's the perfect companion for building a Javascript application.
Cornice features that are really useful in this context:
- CORS support
- based on a robust & mature framework: Pyramid
- dead-simple Mozilla Persona integration, thanks to a vibrant Pyramid ecosystem
- standard machine-parseable errors
- can run everything in async mode
Busting the myth about Python & async programming
Before I talk about the topic, I want to make a little digression and bust a myth about Python vs Javascript.
I have heard this sentence several times in Javascript and Node.js developers circles:
Python doesn't support async programming
I've even heard some people explaining that Python couldn't be used as an async language because of the GIL !
Talking about the GIL in this context is completely out of topic. Like Python, Javascript has something similar to the GIL (locks in "Isolates" in V8). But the GIL becomes an issue only when you run several threads in the same process.
And in web programming, we don't really use threads anymore.
Node.js is single-threaded and uses libuv to run an event loop, and feeds the loop with callbacks.
Python has also libraries and frameworks that provide event loops. In fact, Node.js was inspired by Twisted.
There are libuv, libevent & libev bindings in Python. And frameworks and libraries that use those bindings to provide event loops.
That said, I am not a huge fan of callback programming, I find it quite hard to read and debug. I like it better when I am working with thread-like objects.
Fortunately in Python we have gevent that will provide greenlets, which are pseudo-threads that wrap callbacks and an event loop - and you don't have to do callbacks anymore.
Gevent takes care of the gory details and let you do linear, functional programming that can be traced, debugged, without falling into an horrible maze of callbacks.
A code like this...
# from https://github.com/SiteSupport/gevent/blob/master/examples/concurrent_download.py
import gevent
from gevent import monkey
monkey.patch_all()
import urllib2
urls = ['http://www.google.com', 'http://www.yandex.ru', 'http://www.python.org']
def print_head(url):
print ('Starting %s' % url)
data = urllib2.urlopen(url).read()
print ('%s: %s bytes: %r' % (url, len(data), data[:50]))
jobs = [gevent.spawn(print_head, url) for url in urls]
gevent.wait(jobs)
...will asynchronously download all URL pages, and let you work with greenlets.
Some people don't like this approach and use tools like Tornado, that will let you start an event loop and define callbacks, like Node.js.
But enough said - my point is made:
Yes, the Python ecosystem has the same tools than the Javascript/Node.js ecosystem to do async programming. And it has much more to offer in fact.
Cornice for the JSON APIs
For the Marketplace, we're working on building a separated application to provide a metrics dashboard.
The goal is to display interactive dashboards filled with some Highcharts based charts.
The primary goal is to replace the ones we have in the Marketplace application, that give to the users info like the number of downloads for their web apps.
For this, we're going to provide a set of JSON APIs on the top of an Elastic Search server, using Cornice.
Cornice acts as a proxy in front of the Elastic Search server, but also provides a few extra APIs and features we need - it also integrates the excellent pyelasticsearch library.
To give you an idea of how hard is to build such an application with Cornice, here's the core of the code:
es = Service(name='elasticsearch', path='/es')
@es.post(validators=(valid_json_body,), renderer='json')
def query_es_time(request):
try:
return request.es.search(request.validated['body'], index='time_*')
except ElasticHttpError as e:
request.response.status = e.status_code
return e.error
The Service class provided by Cornice does a lot of automation here, like sending back a clean JSON error message in case the query is malformed. It also checks that we don't return a JSON list - since that can be a security hole. It makes sure the server returns a 405 if it's called with the wrong method, etc.
You get the idea: Cornice takes care of the things we never think about, and don't want to think about.
AngularJS for the client-side
I tried out Ember.js and quickly disliked the way the templating works in it, and the fact that they define objects for every element you want to add in the DOM.
Cedric gave a more detailed comparison of Ember vs Angular, and I really liked how Angular looked, so I gave it a shot and instantly liked it.
Angular will let you define new DOM directives, that get expanded on the client side at runtime.
For the dashboard, it means I can define something like this:
<dasboard server="http://data.marketplace.mozilla.org/"> <chart title="Downloads" type="series" field="downloads_count"/> </dashboard>
And have a nice Highchart dashboard that grabs data out of the Cornice server that's behind http://data.marketplace.mozilla.org (Fake URL!)
Defining directives in Angular is done by providing an HTML template and a bit of Javascript glue code.
In our case we also make the chart directive a sub-directive of the dashboard directive - here's an extract of the code so you get an idea:
var app = angular.module('components', []); app.directive('dashboard', function() { return { restrict: 'E', scope: {}, transclude: true, controller: function($scope, $element, $attrs) { this.server = $scope.server = $attrs.server; var charts = $scope.charts = []; this.addChart = function(chart) { charts.push(chart); } }, template: '<div class="tabbable">' + '<h3>Monolith Dashboard</h3>' + '<div class="tab-content" ng-transclude></div>' + '</div>', replace: true }; });
Full code: https://github.com/mozilla/monolith/blob/master/monolith/media/app.js#L30
What I like about Angular is that it's easy to build something that's based on a collection of Plain Old Javascript Objects, so I actually made a separate library that takes care of creating a chart and interacting with the server and the user, given a few tags ids: https://github.com/mozilla/monolith/blob/master/monolith/media/lib/monolith.js#L194
On testing Javascript
I had no idea what was the state of the art for testing Javascript applications since I am a backend developer, so I used what the Angular.js team use and partially built: Testacular & Jasmine.
Testacular is a nice command-line too that will spawn a Firefox instance and run your tests in it. It has nice features like auto-running when a JS file changes, and you can remote-controll it because it uses a Node.JS server to provide interactions.
Although, one thing that annoys me in Javascript (as opposed to Python), is the fact that it's not easy to run processes in your tests fixtures.
What I needed to do is:
- run an Elastic Search server & add content in it
- run the Monolith Server
- then, run the JS tests.
In Python-land, all of this can happen in your test classes. In Javascript, unless I missed the obvious, I had to wrap it in a Makefile: https://github.com/mozilla/monolith/blob/master/Makefile#L30
It's still a bit clunky because I cannot garantee the two servers are really stopped. I should do something better. Maybe I'll end-up wrapping testacular in a Python unit tests... :)
Overall, I quite like building this kind of applications - and I think this pattern of having a light Python web service on the server side, and some kind of JS MVC-based tool on the client-side, is soon going to be the norm.
[sciunto] Et si github fermait ?
[tarek] FaitMain magazine #2
We've launched the second issue of Fait Main, a French magazine about the DIY movement. There are a lot of rough edges remaining since the magazine is built on our week-ends, but it's starting to look good. I love the content of the second issue because it talks about very different topics and not only the usual suspects (Python, Raspberry-PI, Arduino.)
One thing I want to mention to all my English speaking friends: we also accept articles in English - we take care of the translation.
This issue has two articles that were originally in English:
The first one appeared on my radar in Hacker News, and I asked the author the permission to translate it and publish it in the magazine. The second one was written by a member of the Python community, Fritz van Deventer I met during the FOSDEM in a corridor session. I was impressed by his work and asked him to write a little article for our magazine.
So, if you want to write something for the third issue, or know a great article we should translate - let me know in the comments.
Issue #3 will be out in August.
[anybox] Python : comprendre les docstrings en 1 minute
[carlchenet] Les nouveautés de Python 3.3 (GLMF)
[afpyro] AFPyro à Lyon - le 22 mai 2013
Un Afpyro aura lieu le mercredi 22 mai à partir de 20h à l’Antre Autre - 11 rue Terme - 69001 Lyon.
Arthur Vuillard fera une présentation éclair sur Django, le fameux framework web pour les perfectionnistes. Cette présentation donnera suite à une discussion sur les diffèrents frameworks webs disponibles en Python.
L’Antre Autre est un lieu où nous pouvons discuter autour d’un verre, et, pour ceux qui le souhaitent, prendre un repas.
- Pour se rendre à l’Antre Autre :
- en métro : arrêt Hôtel de Ville
- en bus : lignes C13 et C18 arrêt Mairie du 1er ou lignes 19, C14 et C3 à l’arrêt Terreaux
- en vélo’v : stations Place Sathonay, Carmélites Burdeau, Place de la paix