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...

Dites non aux DSL 6   Recently updated !

vendredi 17 mars 2017 à 13:11

Un Domain Specific Language est un langage créé pour une tache très particulière. CSS, HTML et SQL sont des bons exemples de DSL populaires. Moins connus: ReactQL, QML, Less, Latex, XPath, Graphviz…

Suite a ce tuto sur la fabrications d’un DSL en Python, je réalise que quelques personnes recommandent encore de créer des DSL.

Il faut absolument que je vous empêche de commettre cette erreur irréparable !

Une API n’est pas un DSL

Ruby, qui a des parenthèses optionnelles et la capacité d’intercepter les accès d’attributs à la volée, a lancé la mode du mot DSL à tout va. Sauf que la plupart des DSL en Ruby n’en sont pas. Ce sont juste des API écrites en Ruby.

You keep using that world, I don't think it means what you think it means

You killed my project, prepare to die

Enchaîner les méthodes et opérateurs overloadés dans un langage populaire n’est pas utiliser un DSL. foo.bar().baz() n’est PAS un DSL si ça tourne dans la VM du langage. Quel que soit l’enrobage syntaxique qu’on a rajouté.

Un format de sérialisation générique n’est pas un DSL: XLM et YML n’en sont pas, contrairement à ce qu’on peut lire. Car ils sont généralistes, et donc pas “domain spécific” par nature.

Par contre on peut écrire un DSL en JSON, XML ou YAML, et il en existe d’ailleurs pas mal. MathML et SVG sont des exemples de DSL écrits en XML.

Ne créez jamais un DSL

Pourquoi avez vous besoin d’un nouveau langages ? Il existe des centaines de langages et encore plus de formats de sérialisation. Aucun d’entre eux ne peut répondre à votre besoin ? Vraiment ? Vous avez tout testé pendant un mois ?

The world is the problem, the atomic bomis the answer

Choisir le bon outil, Civ style

Créer un DSL , donc un nouveau langage implique vous avez besoin:

Et il faut tenir tout ça à jour.

Ah. Ah. Ah.

Personne ne le fait jamais, ce qui font l’usage de la plupart des DSL un enfer, sans parler de la maintenance des projets qui l’utilisent sur le long terme. Mais les créateurs de DSL s’en effeuillent amoureusement les génitoires car ils seront dans une nouvelle boite quand ça arrivera. Eux il se sont bien amusé a créer un parseur pour le fun. D’ailleurs ça fait classe sur le CV.

Fuck everything

Réponse standardisée aux propositions de DSL. Aussi valable pour l’introduction d’une nouvelle stack Javascript

Créez une API

Votre langage de prédilection est turing complet. Il est donc capable de gérer ce que fait le DSL. Libérez l’anus de cette mouche et traitez votre problème avec un langage supporté, éprouvé, avec un large écosystème et qui résout déjà tout un tas de problèmes.

Ce qu’il vous faut, c’est faire une belle API, pour rendre la tache agréable:

Rayon bien ran

Avec une belle présentation, un truc standard ennuyeux devient soudainement très attractif

Si créer une API n’aide pas assez, par exemple vous faites outil avec un langage haut niveau et avez besoin d’un langage haut niveau pour le scripting ou la configuration, embarquez un langage haut niveau connu. Par exemple Lua ou Python. Ne faites pas comme varnish ou nginx qui ont des fichiers de configuration dans un langage imbitable, indébuggable et mal documenté.

Quelques bonnes raisons de créer un DSL

Aller, il ne faut jamais dire jamais, et comme toujours en informatique, il y a parfois une bonne raison de faire quelque chose. Voici donc la liste des RARES cas où écrire un DSL a du sens:

Mais dans tous ces cas, ne faites un DSL que si:

Special snowflake award

“Mais moi c’est pas pareil”, le hello word du DSL pour prendre des mauvaises décisions dans la vie

devpy, outils de dev à l’arrache   Recently updated !

mercredi 1 mars 2017 à 17:21

Je le publie en coup de vent car je me dis que ça peut servir à des gens, mais j’ai pas l’intention d’y passer des heures… vu que je fais ça pour me faire gagner du temps en mission.

J’ai créé devpy, un outil pour me faciliter le développement.

Dans sa forme la plus simple:

pip install devpy

Ca donne automatiquement:

En gros quand on commence à bosser on fait:

import devpy.develop as log

Et on peut logger comme avec les logs python habituels (log.info, log.debug, etc).

On peut facilement retirer devpy une fois qu’on a fini et le remplacer par un log Python traditionnel type:

log = logging.getLogger(__name__)

Donc utiliser devpy ne coûte rien car on peut le retirer facilement une fois qu’on a fini: c’est 100% compat avec le log de la stdlib.

Ça tombe bien, car c’est pas un truc qu’on met en prod en théorie (même si ça peut pas abîmer le serveur).

C’est juste un outil que j’ai fais car j’en avais marre:

Je vais sûrement rajouter des trucs dedans au fur et à mesure mais pour le moment ça fait ce que je veux.

Je l’ai mis compatible uniquement 3.6 car j’ai pas envie de me faire chier à tester ailleurs. D’ailleurs j’ai même pas de tests.

Et si vous êtes pas contents, vous pouvez vous mettre ma deadline là où je pense.

“BlockingIOError: [Errno 11] Resource temporarily unavailable” pour Python 3.6 4   Recently updated !

mardi 28 février 2017 à 10:39

La toute première version de Python 3.6 avait un bug assez vicieux qui ne se manifestait que sous certaines conditions, généralement dans un daemon sur un serveur, et en important certains modules qui finissent pas déclencher par réaction en chaîne l’usage de random.

django est concerné.

On tombait dessus généralement assez tard, à la mise en prod, avec un message cryptique:

 
BlockingIOError: [Errno 11] Resource temporarily unavailable 
  
 The above exception was the direct cause of the following exception: 

Traceback (most recent call last): 
   .... <- des imports de votre code qui ne font rien de mal
   File "/usr/local/lib/python3.6/site-packages/pkg_resources/__init__.py", line 36, in  
     import email.parser 
   File "/usr/local/lib/python3.6/email/parser.py", line 12, in  
     from email.feedparser import FeedParser, BytesFeedParser 
   File "/usr/local/lib/python3.6/email/feedparser.py", line 27, in  
     from email._policybase import compat32 
   File "/usr/local/lib/python3.6/email/_policybase.py", line 9, in  
     from email.utils import _has_surrogates 
   File "/usr/local/lib/python3.6/email/utils.py", line 28, in  
     import random 
   File "/usr/local/lib/python3.6/random.py", line 742, in  
     _inst = Random() 
 SystemError:  returned a result with an error set 

Cela a été corrigé rapidement, et le binaire patché ajoute juste un “+” à sa version:

$ python --version
Python 3.6.0+

En théorie vous ne pouvez pas tomber dessus, tous les liens de téléchargement ont été mis à jour, les distributions ont changé leurs dépôts, etc.

Mais hier je me suis fait bien niqué, et j’ai perdu 1h à debugguer cette surprise qui n’avait aucun sens (puisque mon code allait bien) : les bugs dans les binaires officiels sont rares et c’est le dernier endroit où je cherche.

En effet, certaines sources non-officielles pour installer Python n’ont pas été mise à jour, et c’est le cas du très populaire PPA deadsnake.

Si vous avez installé Python 3.6 en faisant :

sudo add-apt-repository ppa:fkrull/deadsnakes
sudo apt-get update
sudo apt-get install python3.6

Vous l’avez dans le cul.

Il existe un PPA plus à jour si vous avez besoin de corriger le tir :

sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get update
sudo apt-get install python3.6

Donc si vous avez compilé Python à la main ou utilisé un PPA, assurez-vous bien d’avoir la bonne version, et sinon upgradez. En attendant j’ai un bug report à faire à deadsnakes…

Quelques outils pour gérer les clés secrètes en Django 3   Recently updated !

jeudi 23 février 2017 à 16:05

On ne veut pas mettre sa SECRET_KEY en prod, et utiliser un service pour générer la clé, ça va deux minutes.

Générer une clé secrète:

import random
import string
 
def secret_key(size=50):
    pool = string.ascii_letters + string.digits + string.punctuation
    return "".join(random.SystemRandom().choice(pool) for i in range(size))

Générer une clé secrete avec une commande manage.py:

from django.core.management.base import BaseCommand, CommandError
from polls.models import Question as Poll
 
class Command(BaseCommand):
    help = 'Generate a secret key'
 
    def add_arguments(self, parser):
        parser.add_argument('size', default=50, type=int)
 
    def handle(self, *args, **options):
        self.stdout.write(secret_key(options['size']))

A mettre dans ./votreapp/management/command/generate_secret_key.py :)

Une fonction pour lire la clé depuis un fichier texte ou générer la clé si elle n’existe pas:

import io
import os
 
try:
    import pwd
except ImportError:
    pass
 
try:
    import grp
except ImportError:
    pass
 
 
def secret_key_from_file(
        file_path, 
        create=True, 
        size=50, 
        file_perms=None, # unix uniquement
        file_user=None, # unix uniquement
        file_group=None # unix uniquement
    ):
    try:
        with io.open(file_path) as f:
            return f.read().strip()
    except IOError as e:
        if e.errno == 2 and create:
            with io.open(file_path, 'w') as f:
                key = secret_key(size)
                f.write(key)
 
            if any((file_perms, file_user, file_group)) and not pwd:
                raise ValueError('File chmod and chown are for Unix only')
 
            if file_user:
                os.chown(file_path, uid=pwd.getpwnam(file_user).pw_uid)
 
            if file_group:
                os.chown(file_path, gid=grp.getgrnam(file_group).gr_gid)
 
            if file_perms:
                os.chmod(file_path, int(str(file_perms), 8))
 
            return key
 
        raise

Et une fonction pour récupére la clé depuis une variable d’environnement ou un fichier:

def get_secret_key(
        file_path=None, 
        create=True, 
        size=50, 
        file_perms=None, 
        file_user=None, 
        file_group=None,
        env_var="DJANGO_SECRET_KEY"
    ):
    try:
        return os.environ[env_var]
    except KeyError:
        if file_path:
            return secret_key_from_file(file_path, create=create, size=size)
        raise

Le but de cette dernière est d’avoir ça dans son fichier de settings:

SECRET_KEY = get_secret_key('secret_key')

Et de foutre ‘secret_key’ dans son .gitignore.

Comme ça:

En attendant, j’ai proposé qu’on ajoute ça a django extensions. Et qui sait, dans le core peut être un jour ?

Proposition d’un mot clé pour l’évaluation paresseuse en Python 3.7   Recently updated !

dimanche 19 février 2017 à 18:19

Si il y a bien une mailing-list à suivre, c’est Python-dev. Elle regorge de tout, on est y apprend sans cesse à propos de Python, la programmation en général, la gestion de communautés, etc. Mais c’est accessible pour peu qu’on soit à l’aise en anglais.

Parmi les sujets chauds du moment, il y a l’introduction, pour potentiellement Python 3.7, d’un mot clé pour évaluer paresseusement les expressions.

Je m’explique…

On a déjà plusieurs moyens de faire du lazy loading en python :

La nouvelle proposition est quelque chose de différent : permettre de déclarer une expression arbitraire, mais qui n’est évaluée que la première fois qu’on la lit.

Ca ressemble à ça:

def somme(a, b):
    print('coucou')
    return a + b
 
truc = lazy somme(a, b)
print("Hello")
print(truc)

Ce qui afficherait:

Hello
coucou
3

On peut mettre ce qu’on veut après le mot clé lazy. Le code n’est exécuté qu’une fois qu’on essaye d’utiliser la variable truc.

L’usage essentiel, c’est de pouvoir déclarer du code sans chichi, comme si on allait l’utiliser maintenant. Le passer à du code qui va l’utiliser sans même avoir besoin de savoir que c’est un truc spécial. Et que tout marche à la dernière minute naturellement.

Par exemple, la traduction d’un texte dans un code Python ressemble souvent à ça :

from gettext import gettext as _
...
print(_('Thing to translate'))

Mais dans Django on déclare un champ de modèle dont on veut pouvoir traduire le nom comme ceci :

from django.utils.translation import ugettext_lazy
 
class Produit(models.Model):
    ...
    price = models.IntegerField(verbose_name=ugettext_lazy("price"))

La raison est qu’on déclare ce texte au démarrage du serveur, et on ne sait pas encore la langue dans laquelle on va le traduire. Cette information n’arrive que bien plus tard, quand un utilisateur arrive sur le site. Mais pour détecter toutes les chaînes à traduire, créer le fichier de traduction, construire le cache, etc., il faut pouvoir marquer la chaîne comme traductible à l’avance.

Django a donc codé ugettext_lazy et tout un procédé pour évaluer cette traduction uniquement quand une requête Web arrive et qu’on sait la langue de l’utilisateur.

Avec la nouvelle fonctionnalité, on pourrait juste faire:

from gettext import gettext as _
 
class Produit(models.Model):
    ...
    price = models.IntegerField(verbose_name=lazy _("price"))

Rien à coder nul part du côté de Django, rien à savoir de plus pour un utilisateur. Ça marche dans tous les cas pareil, pour tout le monde, dans tous les programmes Python.

Bref, j’aime beaucoup cette idée qui permet de s’affranchir de pas mal de wrappers pour plein de trucs, mais aussi beaucoup aider les débutants. En effet les nouveaux en programmations font généralement des architectures basiques : pas d’injection de dépendances, pas de factories, etc. Avec lazy, même si une fonction n’accepte pas une factory, on peut quand même passer quelque chose qui sera exécuté plus tard.

Évidement ça ne dispense pas les gens de faire ça intelligemment et d’attendre des callables en paramètre. Dans le cas de Django, une meilleure architecture accepterait un callable pour verbose_name par exemple.

Mais c’est un bon palliatif dans plein de situations. Et l’avantage indiscutable, c’est que le code qui utilise la valeur paresseuse n’a pas besoin de savoir qu’elle le fait.

Les participants sont assez enthousiastes, et moi aussi, bien que tout le monde a conscience que ça pose plein de questions sur la gestion des générateurs, de locals(), et du debugging en général.

Plusieurs mots clés sont actuellement en compétition: delayed, defer, lazy. delayed est le plus utilisé, mais j’ai un penchant pour lazy.

Viendez sur la mailing list !

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/?46 #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}