PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

Crossbar, le futur des applications Web Python ?

dimanche 25 mai 2014 à 12:24

Je suis crossbar.io depuis quelques temps maintenant, et je suis très, très étonné de ne pas plus en entendre parler dans la communauté Python.

Bon, en fait, à moitié étonné.

D’un côté, c’est une techno qui, à mon sens, représente ce vers quoi Python doit se diriger pour faire copain-copain avec Go/NodeJs et proposer une “killer feature” dans le monde des applications serveurs complexes.

De l’autre, hum, leur page d’accueil explique à quoi ça sert de cette manière :

Crossbar.io is an application router which implements the Web Application Messaging Protocol (WAMP). WAMP provides asynchronous Remote Procedure Calls and Publish & Subscribe (with WebSocket being one transport option) and allows to connect application components in distributed systems

Moui, moui, moui monseigneur, mais concrètement, là, hein, je peux faire quoi avec ?

C’est toujours le problème avec les gens intelligents (hein Cortex ?) : ils font des trucs super cool, et personne ne comprend à quoi ça sert parce qu’il ne sont pas foutu de l’expliquer.

Moi je suis un peu con, alors je vais profiter qu’on soit tous au même niveau pour vous faire passer le message.

J’étais persuadé d’avoir mis la musique habituel… Je la remet:

Web Application Message Protocol

Je vous avais parlé d’autobahn dernièrement, un client WAMP qui embarque aussi un routeur basique. Crossbar est la partie serveur, un routeur WAMP plus serieux.

Crossbar permet à tous les clients d’échanger des messages WAMP à travers lui. Bien entendu, un client WAMP peut parler au serveur crossbar et inversement comme un client HTTP peut parler à un serveur Apache/Nginx et inversement. Mais plus que ça, les clients peuvent parler entre eux, de manière transparente et simple. Comme un client AMQP peut parler aux autres à travers un serveur RabbitMQ.

Cependant ça ne vous avance pas si vous ne savez pas ce qu’est WAMP ou à quoi ça sert. La charrue avec la peau de l’ours, tout ça.

WAMP est un protocole standardisé pour échanger des messages entre deux systèmes. Ce n’est pas particulièrement lié à Python, on peut parler WAMP dans n’importe quel langage.

Il fonctionne en effet, principalement, au dessus de Websocket, donc on peut l’utiliser directement dans le navigateur, dans Firefox, Chrome, Opera, Safari et même IE10, via une lib Javascript. Qui est en fait juste un gros wrapper autour de l’API websocket standardisant la manière d’envoyer des données. Il n’y a rien de magique derrière, pas de formats compliqués, pas de binaire, c’est vraiment juste des appels websocket contenant des données formatées en JSON avec une certaine convention. En ce sens il fait penser à SockJS et (feu) socket.io.

Seulement contrairement aux solutions type SocketJS, il n’est pas limité au navigateur. Il y a des libs pour l’utiliser dans un code serveur Python, C++ ou NodeJS, dans une app Android et même directement depuis les entrailles de Nginx ou d’une base de données Oracle (en SQL) avec certains routeurs.

Schéma général du fonctionnement de WAMP

Comme HTTP, WAMP est juste un moyen de faire parvenir des données d'un point A à un point B. Mais contrairement à HTTP, WAMP permet aux clients de parler entre eux, et pas juste au serveur.

Comprenez bien, ça veut dire qu’on peut envoyer et recevoir des données arbitraires, en temps réel, entre tous ces systèmes, sans se prendre la tête, et de manière transparente.

WAMP c’est donc comme, mais mieux que :

Bien utilisé, Crossbar permet d’amener Python dans la cours de frameworks “temps réel” novateurs comme MeteorJS, et potentiellement les dépasser.

Car WAMP permet de faire deux choses. Simplement. Et bien.

1 – Du PUB/SUB

Donc de dire dans son code “appelle cette fonction quand cet événement arrive”. C’est comme les signaux de Django ou QT, mais ça marche à travers le réseau. On le fait souvent avec Redis ces temps-ci. Avec WAMP et Javascript, ça donne :

// connection au routeur WAMP (par exemple, crossbar.io)
ab.connect("ws://localhost:9000", function(session) {
    // je m'inscris à un événement
    session.subscribe('un_evenement_qui_vous_plait', function(topic, evt){
        // faire un truc avec les données reçues
        // à chaque fois que l'événement est envoyé
        // par exemple mettre la page à jour
    });
});
Schéma de fonctionnement du subscribe de WAMP

Des client SUBscribe à un événément. Un événément est un nom arbitrairement choisit par le programmeur, et qu'il va déclencher lui-même quand il pense qu'il se passe quelque chose d'important auquel il faut que le reste du signal réagisse.

Et ailleurs, dans une autre partie du fichier, ou même sur un autre navigateur Web :

ab.connect("ws://localhost:9000", function(session) {
    // création d'un événement auquel on attache des données
    session.publish('un_evenement_qui_vous_plait', ['des données']);
Schéma de fonctionnement de PUB avec WAMP

Le programmeur décide que quelque chose d'important arrive (création d'un contenu, login d'un utilisateur, notification), et PUBlish l'événement

Et oui, c’est tout. On se connecte à crossbar, et on discute. La fonction du subscribe sera alors appelée avec les données du publish. Même si il y a 3000 km entre les deux codes. Même si le code A est sur un navigateur et le B sur un autre, ou sur un serveur NodeJS, ou une app Android.

Ce qui fait peur au début, c’est qu’il y a TROP de flexibilité :

Mais en fait c’est super simple : un événement c’est juste une action de votre application comme un élément (un post, un commentaire, un utilisateur…) ajouté, supprimé ou modifié. Finalement c’est le bon vieux CRUD, mais en temps réel, et en push, au lieu du pull. Vous choisissez un nom qui représente cette action, vous attachez des données à ce nom, voilà, c’est un événement que tous les abonnés peuvent recevoir.

Avec un bonus : ça marche sur le serveur aussi ! Votre code Python reçoit “ajouter un commentaire” comme événement ? Il peut ajouter le commentaire en base de données, envoyer un message à un service de cache ou à un autre site en NodeJS pour le mettre à jour, renvoyer un événement pour mettre à jour les pages Web et l’app Android, etc.

On peut passer n’importe quelles données qui peut se JSONiser. En gros n’importe quoi qu’on enverrait via HTTP. Donc des données très structurées, imbriquées et complexes comme des données géographiques, ou très simples comme des notifications.

Avec PUB / SUB, WAMP remplace tout ce qu’on ferait normalement avec des appels AJAX dans le browser, et tout ce qu’on ferait avec des files de message côté serveur. Plus puissant encore, il permet de relier ces deux mondes.

Et même si on atteint pas les perfs de ZeroMQ (qui n’a pas de serveur central), c’est très performant et léger.

2 – Du RPC

Appeler une fonction située ailleurs que dans son code. C’est vieux comme le monde (si vous avez des souvenirs douloureux de CORBA et SOAP, levez la main), et c’est extrêmement pratique. Pour faire simple, continuons avec un exemple en Javascript, mais rappelez-vous que ça marche pareil en C++ ou Python :

ab.connect("ws://localhost:9000", function(session) {
   function une_fonction(a, b) {
      return a + b;
   }
   // on déclare que cette fonction est appelable à distance
   session.register('une_fonction', une_fonction);
});
Schéma expliquant register avec WAMP

RPC marche à l'envers de PUB/SUB. Un client expose du code, et un autre demande explicitement qu'il soit exécuté.

Côté appelant :

ab.connect("ws://localhost:9000", function(session) {
    // on appelle la fonction à distance, on récupère une
    // promise qui nous permet de travailler sur le résultat
   session.call('une_fonction', 2, 3).then(
      function (res) {
         console.log(res);
      }
   );
Schéma expliquant CALL en WAMP

Contrairement à PUB/SUB, RPC ne concerne que deux clients à la fois. Mais ça reste asynchrone. Le client demandeur n'attend pas le résultat de l'appel de la fonction. Il est signalé par le serveur quand il est prêt.

Pareil que pour le PUB/SUB, les gens ont du mal à voir l’utilité à cause du trop de flexibilité que ça apporte. Imaginez que votre projet soit maintenant éclaté en de nombreux petits services qui tournent et qui sont indépendants :

Tous ces services peuvent ainsi communiquer entre eux via RPC, mais n’ont pas besoin d’être dans le même processus. On peut profiter pleinement de tous les cœurs de sa machine, on peut même les mettre sur des serveurs séparés.

Mieux, avoir un service bloquant ne pénalise pas tout le système. En effet, un problème avec les systèmes asynchrones en Python est que beaucoup de libs sont encore bloquantes (typiquement les ORMs). Avec ce genre d’architecture, on peut créer un service qui ne fait que les appels bloquant et laisser les autres services non bloquant l’appeler de manière asynchrone. Pendant qu’il bloque, le reste du système peut traiter d’autres requêtes.

Crossbar, plus qu’un routeur WAMP

L’idée des concepteurs de crossbar est de permettre de créer des systèmes avec des services composables qui communiquent entre eux plutôt que tout dans un gros processus central. Ils ne se sont donc pas arrêtés au routing.

Crossar est également un gestionnaire de processus, comme supervisor ou, plus légitimement, circus (Tarek, fait une pause, vient ici !) et sa communication ZeroMQ.

Il se configure avec un simple fichier JSON, et on peut y définir des classes Python qui seront lancées dans un processus séparé et pourront discuter avec les autres clients via WAMP :

{
   "processes": [
      { // premier processus
         "type": "worker",
         "modules": [
            {
               un_worker.Classe
            },
            {
               un_autre_worker.Classe
            }
         ]
      },
      {  // second processus
         "type": "worker",
         "modules": [
            {
               un_autre_worker_dans_un_autre_process.Classe
            }
         ]
      }
   ]
}

Mais si ça ne suffit pas, on peut également lancer des programmes extérieurs non Python dont crossbar va gérer le cycle de vie :

{
   "processes": [
      {
         "type": "guest",
         "executable": "/usr/bin/node",
         "arguments": ["votre_script.js"],
         "stdout": "log"
      }
   ]
}

Vous avez donc ainsi les deux atouts pour avoir une architecture découplée, scalable, exploitant plusieurs cœurs, et compensant en partie les bibliothèques blocantes :

Cas concret

WAMP est typiquement le genre de techno qui ne permet PAS de faire quelque chose qu’on ne faisait pas avant. Ce n’est pas nouveau.

En revanche, WAMP permet de le faire mieux et plus facilement.

Prenez le cas d’un utilisateur qui se connecte sur un forum. Il va sur un formulaire, il post ses identifiants, ça recharche la page, il est connecté. Si les autres utilisateurs rechargent leurs pages, ils verront un utilisateur de plus connecté.

Si on veut rendre ça plus dynamique, il faut utiliser de l’AJAX, et si on veut avoir une mise à jour presque en temps réel, il faut faire des requêtes Ajax régulière. Ce qui est assez bancal et demande beaucoup de travail manuel.

Certains sites modernes utilisent Websocket, et des serveurs asyncrones comme NodeJS, et un routeur PUB/SUB comme redis, pour faire cela de manière rapide et plus facile. L’application est très réactive. Mais le système est hétéroclite. Et si on veut envoyer des messages entre des composants serveurs, ça demande encore quelque chose de différent.

WAMP unifie tout ça. Un coup de RPC pour le login pour effectuer l’action:

Schéma d'un exemple concret de RPC WAMP

Notez que le RPC marche de n'importe quel client à n'importe quel client. Il n'y a pas de sens obligatoire. Le login est un exemple simple mais on peut faire des choses bien plus complexes.

Et un coup de PUB/SUB pour prévenir tout le monde que quelque chose s’est passé :

Schéma d'exemple concret de PUB/SUB avec WAMP

Je n'ai mis que des clients légers ici, mais je vous rappelle qu'un client peut être un serveur NodeJS, une base de données, un code C++...

Bien entendu, on pourrait faire ça avec les technos existantes. C’est juste moins pratique.

Notez également que Crossbar encourage à avoir un service qui ne se charge que du login, sans avoir à faire une usine à gaz pour cela. Si demain votre service de login a besoin d’être sur un coeur/serveur/une VM séparé pour des raisons de perfs ou de sécurité, c’est possible. Crossbar encourage ce genre de design.

Voici où est le piège

Car évidement, il y en a toujours un, putain de métier à la con.

Et c’est la jeunesse du projet.

Le projet est stable, le code marche, et les sources sont propres.

Mais la doc, mon dieu la doc… Les exemples sont pas à jour, il y a deux versions qui se battent en duel, on sait pas trop quelle partie sert à quoi.

Et comme tout projet jeune, l’API n’a pas été assez étudiée. Or la partie Python est basée sur Twisted, sans polish. Twisted, c’est puissant, c’est solide, et c’est aussi une API dégueulasse.

Un exemple ? Comment écouter un événement :

# Des imports légers
from twisted.python import log
from twisted.internet.defer import inlineCallbacks
 
from autobahn.twisted.wamp import ApplicationSession
from autobahn.twisted.wamp import ApplicationRunner
 
# Une bonne classe bien subtile pour copier Java
class ListenForEvent(ApplicationSession):
 
    # Deux méthodes de boiler plate obligatoires
    # et parfaitement soulantes pour l'utilisateur
    # final. Cachez moi ça bordel !
    def __init__(self, config):
        ApplicationSession.__init__(self)
        self.config = config
 
    def onConnect(self):
        self.join(self.config.realm)
 
    # Bon, on se décide, soit on fait une classe avec des noms
    # de méthode conventionnés, soit on met des décorateurs, 
    # mais pas les deux, pitié !
    @inlineCallbacks
    def onJoin(self, details):
        callback = lambda x: log.msg("Received event %s" % x)
        yield self.subscribe(callback, 'un_evenement')
 
# Python doit lancer explicitement un event loop.
# Ca pourrait (devrait) aussi être embeded dans une
# sousclasse de ApplicationSession.
# /me prend un colt. BANG !
if __name__ == '__main__':
   runner = ApplicationRunner(endpoint="tcp:127.0.0.1:8080",
                              url="ws://localhost:8080/ws",
                              realm="realm1")
   runner.run(ListenForEvent)

C’est la raison pour laquelle je vous ai montré le code JS et pas Python pour vous vendre le truc. Sur sametmax.com, on aura tout vu :(

Voilà à quoi devrait ressembler le code Python si l’API était mature :

from autobahn.app import App
 
app = App(url="ws://localhost:8080/ws")
 
@event("un_evenement")
def handle(details):
    app.log("Received event %s" % x)
 
if __name__ == '__main__':
   app.run()

Chose que je vais proposer sur la mailing list (ils sont réactifs et sympas, vive les allemands !) dans pas longtemps. Et si ils n’ont pas le temps de le faire, il est possible que je m’y colle. Ca me fait mal aux yeux.

flattr this!

Les annonces hypes

samedi 24 mai 2014 à 06:04

Nous sommes une start up innovante

on a pas de business model

à fort potentiel

c’est ma première boîte et ma mère me dit que j’ai toutes mes chances

travaillant à la pointe de la pointe de la technologie

mon dernier dev a créé un projet NodeJS sans documentation ni commentaire et s’est barré

en utilisant des méthodes agiles.

on a pas de cahier des charges ni de formation à l’embauche

Nous somme à la recherche d’un dev ninja passionné et autonome

on veut un mec qui travaille 80 heures / semaine pas cher à payer

capable de mener le projet de bout en bout.

je ne sais pas parler au client, coder ou même conduire un projet. Je ne sais rien faire. Je ne sert à rien. J’ai juste eu une idée un soir d’apéro. A l’aide.

Vous serez en mesure de travailler avec Linux, HTML5, Postgres, Flask, NodeJS, Coffeescript, MongoDB, Mémécash, AmazonS3, Notepad++, duckduckgo et VLC.

Je n’ai aucune idée de quoi je parle, alors j’ai mis tout ce que je comprends pas sur la même ligne.

Télétravail possible.

Je ne veux pas parler de la rémunération alors je finis là dessus. Ça sera surement une part des bénéfices potentiels assurés mirobolants. Pas une trop grosse part, c’est quand même moi qui a eu l’idée.

Merde c’est quoi le mail de ma boîte déjà ? Tant pis je met l’adresse hotmail.




Ils prennent whatsapp ? Nan vaut mieux pas, faudrait pas que je passe pour un con.




Je vais rajouter mon num… MERDEEEEEEE. J’ai envoyé le formulaire par erreur.








Ah, c’est pas le bon site en fait.


flattr this!

Le Web n’est plus HTTP + HTML

vendredi 23 mai 2014 à 07:24

Si vous voulez énerver un blogger technophile, utilisez le mot Web là où vous devriez utiliser le mot Internet et inversement.

Internet, c’est beaucoup plus que le Web. C’est SSH, IMAP, TELNET, DNS, POP, SMTP, FTP, RTSP, NNTP, Bittorent, TOR, Freenet, Bitcoin, et quelques centaines d’autres protocoles qui se parlent.

Jusqu’ici, le Web, c’était juste HTTP. Des ressources Web, sur lesquelles on agissait via une requête textuelle verbalisée (GET, POST, PUT, OPTION, HEAD, etc) et qui retournait une réponse, généralement en forme de HTML.

Ça a un peu évolué, on a eu SSL qui s’est rajouté, et donc HTTPS, et AJAX, qui n’a pas changé le protocole, mais rendu la nature des requêtes un peu différente. Rien qui n’empêche de debugger avec CURL.

Mais c’est bientôt fini tout ça.

Aujourd’hui les nouveaux protocoles utilisés dans le cadre du Web sont en passe de prendre le pouvoir. Bien sûr il y a SPDY et QUIC, mais surtout, il a les protocoles basés sur les websockets type WAMP.ws, mais également les nouvelles capacités P2P promises par WebRTC. Et des apps qui utilisent massivement les données hors ligne, le scripting JS pour des features essentielles, de la video, du son…

Et donc adios, l’époque où vous pouviez juste dégainer requests et parler à un site. Bye bye le state less, le human readable, le cycle requête / réponse.

Le nombre de technologies qu’on doit empiler aujourd’hui pour déployer un site devient énorme : un moteur de recherche, un message broker, un gestionnaire de fil d’attente, un gestionnaire de déploiement, des technos d’isolation…

C’est fini la simplicité. C’est fini la transparence. L’ère du hacker amateur qui pouvait s’occuper d’un peu de tout, touche doucement à sa fin.

Au revoir et merci. Je me suis super amusé.

Et désolé pour les mômes qui arrivent maintenant, vous allez en chier. Mais vous avez aussi plus de possibilités qu’il n’y en a jamais eu. Plus qu’un homme ne peut concevoir. Plus que tous les hommes en fait.

Et RIP HTTP. Ou pas, puisqu’on passe notre temps à faire des APIs REST maintenant, mais aussi car on est en train de récréer un peu tout au dessus d’HTTP. Long live HTTP, alors, le nouveau TCP/IP. Sauf quand on fait du real time. Ou du P2P. Changement de status : “c’est compliqué entre moi et mon navigateur”.

Internet, phagocyté par le Web, sur lequel on reconstruit Internet et même le desktop ?

Je ne crois pas qu’il existe un seul métier qui ait autant changé en 10 ans. J’espère qu’on en laisse pas trop derrière en courant comme des fous en riant les yeux mi-clos. Pourvu qu’il y ait pas trop d’arbres en face. Pourvu qu’on aille pas dans la direction de la falaise.

En tout cas, c’est toujours fun. Je crois que je vais descendre la prochaine pente en roulant sur le côté. Et avoir la tête qui tourne. Vomir. Et dire que c’est la faute de Javascript.

Et recommencer.

flattr this!

Rejouer un session de terminal avec playitagainsam

jeudi 22 mai 2014 à 10:29

Combien de fois, durant une formation, on m’a demandé si je pouvais donner un dump de la session shell.

playitagainsam, ou PIAS, a exactement pour but de faire cela: il lance un shell, enregistre ce qu’on y a tapé, et quand on quitte le shell, sauvegarde le tout dans un fichier. Ensuite, on peut lire le contenu du fichier et revoir la session shell telle qu’elle a été tapée.

C’est un programme Python, on peut donc le pip installer :

pip install playitagainsam --user

Ensuite, on lance une session à enregistrer avec la sous commande record :

pias record nom_du_fichier_ou_sauver_l_enregistrement

PIAS va lancer un nouveau shell, par défaut le même que votre shell actuel (souvent bash, pour moi fish), mais vous pouvez préciser un type de shell spécial avec --shell. Ex :

pias record ex1 --shell ipython

A partir de là il n’y a rien à faire. On peut travailler normalement et oublier qu’on a utilisé PIAS. Une fois qu’on a terminé sa petite affaire, il suffit de sortir du shell (CTRL + D, exit, etc), et la session va être sauvegardée dans un format JSON facile à corriger à la main si vous souhaitez la rendre plus agréable :

{
  "events": [
    {
      "act": "OPEN",
      "size": [
        162,
        33
      ],
      "term": "1c53f048e3a74984b2a93af175e24e87"
    },
    {
      "act": "PAUSE",
      "duration": 0.10042214393615723
    },
    {
      "act": "WRITE",
      "data": "\u001b]0;sam@sametmax.com: /tmp\u0007sam@sametmax.com:/tmp$ ",
      "term": "1c53f048e3a74984b2a93af175e24e87"
    },
    {
      "act": "ECHO",
      "data": "touch essai",
      "term": "1c53f048e3a74984b2a93af175e24e87"
...
}

Quand vous voulez rejouer la session :

pias play fichier_d_enregistrement

Par défaut il faut appuyer sur une touche quelconque du clavier pour voir apparaitre chaque caractère, donnant l’impression que vous êtes un hacker de Hollywood. C’est rigolo, mais parfaitement chiant à la longue. On peut faire jouer la session automatiquement avec l’option --auto-type qui va taper chaque ligne automatiquement. Il suffira alors d’appuyer sur entrée à chaque ligne pour avoir le résultat suivant. C’est mon mode préféré.

Si vous êtes un gros feignant, vous pouvez tout jouer en automatique en rajoutant l’option --auto-waypoint qui va vous dispenser de taper entrée à chaque fin de ligne. Ex :

pias play ex1 --auto-type --auto-waypoint

La session jouée est simulée. Elle n’a aucun impacte sur le système. Si vous faites un mkdir dans bash ou un shutil.rmtree dans python, ça ne fera rien.

Néanmoins, parfois il est utile d’avoir le effets réels des commandes. Dans ce cas on peut utiliser --live-replay. Attention, faites bien gaffe à ce que vous allez jouer…

playitagainsam est intéressant pour tout ce qui est formation et tutorial, d’autant plus qu’il existe un player javascript pour faire la démonstration de vos sessions shell sur un blog ou un slideshow.

flattr this!

Qu’est-ce que la bibliothèque standard Python ?

lundi 19 mai 2014 à 11:36

La lib standard ou stdlib, on vous en parle partout. Ce tuto vous explique comment faire telle tâche avec la lib standard. Ce README dit qu’il n’y a que la lib standard comme dépendance, c’est génial ! Cet article voudrait que le code X soit intégré dans la lib standard.

Mais au fait, c’est quoi la lib standard ?

Et bien c’est tout simplement l’ensemble du code qui est disponible si vous avez Python installé sur votre machine. Si un module fait partie de la bibliothèque standard, vous êtes certain que votre programme Python peut toujours l’utiliser : il n’y a rien à installer.

Cette lib standard est également intégralement documentée sur le site de Python, si bien que si vous téléchargez la doc hors ligne, vous pouvez quasiment dev en totale autonomie. En effet, même si le principe de la lib standard est le même pour tous les langages, en Python, elle est particulièrement intéressante car vraiment énorme. Et en plus vous avez le dump de notre blog :)

Le code de la lib standard évolue lentement, contrairement aux libs tierces parties, car elles sont soumises à des règles strictes et la revue de la communauté à travers des documents officiels : les PEP. Le PEP le plus célèbre étant le PEP 8, qui décrit le style recommandé pour du code Python.

De fait le code de la lib standard est un code fiable, sans surprise, mais qui proposera rarement les dernières innovations. Pour reprendre les mots de Guido :

Le code de la stdlib a un pied dans la tombe.

Car Python garanti la compatibilité ascendante de TOUTE la lib standard pour une version majeure. Ce qui veut dire que si vous utilisez un module de la stdlib écrit pour Python 2.5, il marchera en Python 2.7, et un module pour 3.1 marchera pour la 3.4.

Mais ça veut dire aussi que le module pourrira ici quand plus personne ne l’utilisera. La preuve en est getopt, qui a été remplacé par optparse, qui a été remplacé par argparse. Deux générations de libs plus tard, getopt est toujours importable en 2.7, bien que plus personne ne s’en serve, pour garantir cette compatibilité. Et en fait, on peut dire 5 générations, car nous-même utilisons docopt après prôné clize. Le monde de l’IT est comique.

Certains systèmes ébrèchent le principe de la lib standard, comme Ubuntu, qui package la lib TK séparément, et donc oblige à installer le paquet python-tk pour avoir le module tkinter, normalement inclus dans la lib standard. Ce comportement est heureusement assez rare.

flattr this!

Error happened! 0 - count(): Argument #1 ($value) must be of type Countable|array, null given In: /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php:428 http://ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/?101 #0 /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php(999): VroumVroum_Blog->update() #1 /var/www/ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/index.php(1): require_once('...') #2 {main}