Catégorie : développement

Tout ce qui concerne le développement en général, que ce soit des choses générales, ou des choses bien précises. Cela va de la gestion de projet à la recherche du fonctionnement de pointeurs en C.

python, flask, https et wsgi howto

Je ne vais pas dire le temps que j’ai passé à réussir à mettre ça en oeuvre si quelqu’un fait ça en quelques minutes j’aurais honte… mais disons que j’ai passé beaucoup de temps à le faire tourner, mais je suis vraiment impressionné par sa rapidité. Voici – via ma petite expérience – comment faire tourner un serveur Web très optimisé, en mode production  :
  • flask
  • wsgi
  • https

https / certbot

Je vous donne des pistes, à vous de finir.
L’idée, c’est que je veux paramétrer pour la n-ième fois un nouveau nom de domaine via certbot, et je ne veux pas qu’il touche à la conf de nginx. L’idée c’est que :
(1) Je le fais simplement via certbot-auto puis après,
(2) toutes les autres fois j’utilise « -d » pour qu’il ne touche pas à la conf, et je le fais à la main, en m’inspirant de la conf que cert-bot a fait dans le (1)
Donc, pour faire le (1), je demande à certbot de me créer uniquement le certificat : ./certbot-auto certonly -d monsite.fr
Pour le https, regardez ici.

Python

Code exemple, qui lit un fichier JSON, lance le serveur et sert le fichier lu en tant que JSON :

import json

from flask import Flask, jsonify

app = Flask(__name__)

with open("mon fichier JSON", 'r') as f:
    json_levels_full_description = json.load(f)


@app.route('/')
def index():
    return jsonify(json_levels_full_description)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8004)
else:
    application = app

Attention : surtout ne faites pas le classique if __name__ == '__main__' car uwsgi inclut ce fichier, donc ce fichier n’est jamais un __main__, par contre la configuration au lancement a besoin d’une variable globale nommée application.

Nginx

Je copie colle la configuration d’un site qui tourne déjà en https, en voici un extrait diminué à mort pour ne pas polluer avec des règles personnelles, regardez bien le texte en gras, c’est le plus important pour faire tourner Nginx en collaboration avec wsgi :

server {
  server_name "~(www\.)?monsite\.(com|fr|org|biz|be)$";

  index index.html index.htm;

  access_log /blah/proxy-access.monsite.log proxylog;
  error_log /blah/proxy-error.monsite.log error;

  listen 443 ssl;
  ssl_certificate /blah/fullchain.pem;
  ssl_certificate_key /blah/privkey.pem;
  include /blah/options-ssl-nginx.conf;
  ssl_dhparam /blah/ssl-dhparams.pem;

  location / {
    include denied_clients;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Server $host;

    # (!) here it's uwsgi:
    include uwsgi_params;
    uwsgi_pass 127.0.0.1:8002;

  }
}

# redirect http -> https:
server {
  listen *:80;
  server_name "~^monsite\..*$";
  return 302 https://$host$request_uri;
}

Flask

Pour installer flask, et uwsgi, faites un environnement virtuel puis installez les deux :

$ python3 -m venv venvpython.3.6.6
$ source venvpython.3.6.6/bin/activate
$ pip install --upgrade pip
$ pip install flask

uwsgi

Il faut être dans le venv activé (voir paragraphe précédent), puis l’installer via pip :

$ pip install uwsgi

Enfin, le script de lancement !

uwsgi --chdir=/web/htdocs/blah \
    --module=flask_server:application \
    --master --pidfile=/tmp/uwsgi.blah.pid \
    --socket=127.0.0.1:8004 \
    --processes=5 \
    --uid=1000 --gid=2000 \
    --harakiri=20 \
    --max-requests=5000 \
    --vacuum \
    --home=/web/htdocs/blah/venvpython.3.6.6

flask_server correspond au fichier python flask_server.pydécrit dans la section python au début et application est le nom de la variable globale (lisez le code de la section python)

Lorsque vous le lancerez, tout se passera bien, mais l’exécutable ne sera pas en tâche de fond. Cela a un avantage : un CTRL-C et vous l’arrêtez ! L’inconvénient, c’est que vous n’avez pas la main.

Si vous voulez le lancer en tâche de fond et qu’il écrive les logs dans un fichier, ajoutez l’option     --daemonize=/var/log/uwsgi/blah.uwsgi.log


Notes d’amélioration :
– Le port 8004 peut être passé en tant que variable d’environnement à l’application flask via le paramètre `env` de la commande uwsgi, ce qui rend le code plus portable ;
– Les paramètres uwsgi peuvent être consignés dans un fichier my_app.ini et uwsgi serait alors lancé en faisant uwsgi my_app.ini

Python » PyPI

Contribuer à une librairie Python

Principes

  • Faire un dossier (ex : source)
  • Dans source, cloner la librairie à laquelle on veut contribuer via
    git clone [repo concerné]
  • Faire un dossier pour l’environnement pip (ex : pip_env_lib)
  • Dans pip_env_lib, installer l’environnement pip via pipenv install
  • Dans pip_env_lib, lancer le « shell pip » via pip shell
  • Rarement documenté et important :
    – aller dans le dossier source
    – faire pip install -e .

pip install -e . (n’oubliez pas le . qui change tout, cela indique le répertoire à utiliser) signifie « si jamais le shell Python en cours essaie d’accéder à la librairie à laquelle on veut contribuer, aller la chercher dans source ». En fait, concrètement parlant, pip fait simplement un lien symbolique entre le dossier source d’installation de pip (ici c’est pip_env_lib), et le dossier source. N’oubliez pas que cela ne concerne le shell Python en cours (ici pip_env_lib).

L’avantage pratique de ce « lien symbolique », c’est qu’il est directement relié au code source. Donc, lorsqu’on modifie le code source de la librairie elle-même (dossier source), elle est immédiatement disponible. Si on fait donc des fichiers de tests unitaires, il suffit de les relancer, les modifications du code source sont toujours instantanément prises en compte. Toujours dans le shell Python en cours (ici pip_env_lib).

Résumé : exemple de code

mkdir [source]
cd [source]
git clone [repo concerné]
cd ..
mkdir [pip_env]
cd [pip_env]
pipenv --python 3 install
pip shell
pip install -e .

.. et les modifications dans [source] peuvent commencer !


PyPI : nouveau package / contribution

La documentation détaillée en anglais se trouve ici.
Bien meilleure que ce qui suit, mais longue, longue…

Voici les notes prises à la volées, d’un contributeur plutôt habitué à faire des contributions dans le monde Python.
Toute nouvelle note / correction éventuelle est la bienvenue.

Il y a trois choses différentes qu’on a tendance à confondre, mais qui n’ont fondamentalement aucun rapport. On a tendance à les confondre parce qu’on essaie d’utiliser, pour des raisons pratiques, le même nom, mais dans trois cadres différents.
Ces trois choses à bien dissocier sont :
le nom du package PyPI : dans le fichier de déploiement setup.py, il est défini dans le paramètre « name » de la commande setup(). C’est ce qui est vu dans PyPI.
le nom du projet qu’on met dans github : c’est le répertoire qui contient tous les fichiers du projet (= il peut être totalement différent, en général on s’arrange pour les appeler d’un nom différent) = c’est ce qu’on clone dans l’ordre git clone [repo concerné]
– le développeur final qui fait un « import » dans son code : ce module là peut être aussi différent, car c’est du Python. En général, c’est le répertoire de premier niveau du package (il doit contenir, bien sûr, « __init__.py » pour être vu comme un package et importable par Python). En général : « from [ma lib] import [quelque chose]« .

En général, on essaie de s’arranger pour que le nom du package sur PyPI soit le même que le nom du package dans le code Python même.
Mais s’il est déjà pris (dans un des trois contextes), il faut forcément utiliser un autre nom, et c’est là qu’il faut faire attention à ne pas se mélanger les pinceaux.

Pour faire un nouveau package sur PyPI le plus homogène possible :

  1. essayer de faire un projet « vide »
  2. faites une version squelette « alpha1 »
  3. faites une mise à jour sur PyPI : si ça fonctionne, alors vous pouvez continuer. Sinon, il faut utiliser un autre nom

Versionning

Dans PyPI, les versions sont obligatoirement du type «a.b.c».
En général :
[a].[b].[c]
[a] = fonctionnalités totalement nouvelles / incompatibilité
[b] = améliorations / nouvelles "petites" fonctionnalités
[c] = correctifs bogues

setup.py

Lorsque vous faites une nouvelle contribution, vous devez obligatoirement fournir un fichier setup.py.
Dans ce fichier, setup.py, il y a une fonction, ici aussi, obligatoire : setup().
Cette fonction, parmi tous les paramètres, a un paramètre particulier : classifiers qui permet au niveau de PyPI de dire « ce package est rangé dans telle(s) catégorie(s) ». La liste des expressions disponibles se trouve ici.

Django 2.1 : les autorisations de l’administration grandement facilitées

Voici le (petit) souci que tout le monde a forcément dès qu’on commence à vouloir un peu pousser la customization de l’administration Django : on aimerait avoir la possibilité d’accéder à certains modèles, mais uniquement en lecture seule. J’ai des tonnes d’exemples simples, mais très souvent, ce sont des modèles avec des choix « fixes » qui ne risquent pas d’évoluer dans le temps (exemple : sexe « féminin »/ »masculin »). Seulement (1) on ne veut pas le voir apparaître dans l’admin (2) si on ne veut pas le voir apparaître, il ne faut pas le déclarer, et Django ne veut pas accéder à un modèle qui n’est pas déclaré (= erreur), donc on en arrive toujours à (3) on déclare le modèle, on applique une solution « bidouille » que je ne décris pas ici mais même si le modèle n’apparaît pas dans l’interface, en tapant à la main la bonne URL (facile à deviner qui plus est), on peut tout de même modifier ce modèle. Heureusement la version 2.1 de Django pallie à ce manque !

Eh oui, dans la classe interne «Meta», ils ont fait évoluer default_permissions, qui est écrit en base. Donc en passant à Django 2.1 il vous faudra faire le fameux makemigrations migrate.

Là ou cela devient intéressant, c’est que dans les options vous avez default_permissions et elles sont ('add', 'change', 'delete', 'view'). Le plus important et le nouveau est donc 'view' qui pallie parfaitement au problème expliqué en haut !

Il vous suffit de déclarer votre modèle, de faire la classe dérivée de ModelAdmin et de préciser uniquement ('view',).
Attention, je n’ai pas testé, si quelqu’un veut tester et confirmer que ce code fonctionne (ou pas), je suis preneur !

class SexeAdmin(models.ModelAdmin):
    class Meta:
        default_permissions = ('view',)

Plein de code en moins, plus aucune bidouille ici, plus de lisibilité, bref, de mieux en mieux !

weechat

weechat hints / aide

Côté utilisateur

La souris Quand on aimerait cliquer :
/mouse disable
Pour la remettre :
/mouse enable
Documentation Raccourci : cliquer sur le lien :
– Message privé
– Raccourcis clavier

Configuration

Toujours installer v2+ et pas moins https://weechat.org/download/
Fichier de conf :
~/.weechat/
Weechat = une seule instance.
Conf. chargée en entier ici :
~/.weechat/
Pour plusieurs instances, copier tout le dossier, ex :
cp -R ~/.weechat ~/.weechat2
Puis lancez en précisant weechat -d ~/.weechat2
Lancement + connexion auto Exemple de code :
weechat -r '/server add freenode irc.freenode.net; /connect freenode -nicks=Olivier; /set irc.server.freenode.username "Olivier2"; /set irc.server.freenode.realname "Olivier Pons"; /set irc.server.freenode.autojoin "#prognosis-dev"'

Plugins

Situés dans :
~/.weechat/
Plugins Python ici :
~/.weechat/python
Chargement manuel Exemple Python / plugin arespond.py :
/script load arespond.py
Chargement automatique Exemple Python / plugin arespond.py :
Faire un lien symbolique :
cd ~/.weechat/python/autoload
ln -s ../arespond.py arespond.py
Options / switches Définir une option manuellement :
/set plugins.var.python.[SCRIPT_NAME].[option]="[valeur]
Appuyer sur TAB pour autocompléter
Au pire lire les options dans le code même.
Exemple Python / plugin arespond.py :
Les options sont en « camelCase », mais elles sont toutes transformées en minuscules dans weechat.
Il y a trois options marquée dans le code:
"muted"
"respondAfterMinutes"
"responderText"
Donc pour y accéder, il suffit de taper :
/set plugins.var.python.arespond.[tab]
Et les trois options seront affichées sur la ligne du dessus, il suffit d’autocompléter.

Django Async Roadmap

Petite parenthèse sur l’évolution de Django. Aujourd’hui, ce qui commence à importer plus que la vitesse, c’est la parallélisation des choses.
Donc avoir un serveur Web asynchrone, c’est censé être mieux qu’un serveur Web synchrone (= bloquant).

  • Django 2.1 : rien d’asynchrone. Aucun travail dessus.
  • Django 2.2 : travail initial pour ajouter la possibilité de faire de l’asynchrone sur l’ORM et les vues, sachant que c’est une option et que tout sera synchrone par défaut. Le support async sera principalement basé sur des pools de threads.
  • Django 3.0 : Ré-écriture de la gestion interne des requêtes pour être 100% asynchrone, ajout de la gestion asynchrone des middleware, forms, cache, sessions, auth. Début du process de dépréciation pour toutes les APIs prévues pour être uniquement asynchrones.
  • Django 3.1 : amélioration du support async, peut-être changement du templating
  • Django 3.2 : Fin du process de dépréciation et Django sera presque entièrement asynchrone.

Django et git (git « pur », pas « github »)

Si jamais vous décidez d’être un vrai guerrier, un de ceux qui veulent tout maîtriser, un de ceux qui, quand on leur donne des ordres, peuvent les refuser (oui c’est rare), et si vous voulez prendre des responsabilités, bref, si vous en avez : faites vous votre propre serveur git !

Je ne décrirai pas comment installer un serveur git sous Linux tellement c’est simple. Et je ne décrirai pas non plus comment faire ça sous Windows : pour des raisons de sécurité, de fiabilité et d’éthique, évitez Windows un point c’est tout.

Donc, sous Linux :

  • Créez un dossier dans lequel vous mettez toutes vos sources (scp est votre ami si vous êtes sur un PC et que vous voulez faire cela sur un autre PC)
  • Supposons que l’on est dans /web/htdocs/interro/htdocs
  • Créez un fichier .gitignore
  • Dans ce fichier, mettez ceci :
    *.pyc
    __pycache__
    myvenv
    db.sqlite3
    /static
    .DS_Store

    Cela signifie « ignore tous ces fichiers (.DS_Store est pour les personnes sous Mac (non je ne vous en veux pas, je suppose que vous ne connaissez pas bien l’histoire de l’informatique pour avoir acheté Apple)) » pour toutes les personnes qui vont cloner ce dossier
  • Ensuite, ajoutez tous les fichiers ainsi : git add --all .
  • Et commitez (oui je sais ce n’est pas français) ou « appliquez » les modifications c’est à dire l’ajout de tous les fichiers ainsi : git commit -m "My application, first commit"
  • Vous devriez avoir un paquet de fichiers ajoutés, vérifiez que tout est bien enregistré en demandant l’état via git status. Vous devez avoir un messsage ressemblant fortement à
    nothing to commit (working directory clean)
  • Vous êtes prêts ! Vous avez crée un repository (j’ai la flemme de traduire en français), et vous y avez appliqué tous vos fichiers. Il ne vous reste plus qu’à le cloner à distance
  • (Update) : par défaut, le serveur travaille sur la branche d’origine (« master »). Seul problème : si vous essayez de faire un « push » à partir de votre PC de développement, git ne sera pas d’accord pour faire un « push » sur la branche « master ». Je pense que c’est pour des raisons de sécurité ou autre, mais c’est plutôt récent, avant cela fonctionnait. La solution : dites sur le serveur de travailler sur une autre branche c’est via l’ordre checkout. Notez bien : c’est sur le serveur. Je ne trouve pas ça très cohérent, j’aurais dit que c’était au client de dire sur quelle branche il veut « push », mais je dois manquer quelque chose… j’ai vraiment beaucoup de lacunes git. Bref. Exemple : git checkout -b development.
  • Ensuite il vous suffira de faire un git clone (dans PyCharm, c’est le menu VCS » git » clone)

Bien sûr, comme tous les gestionnaires de version délocalisés, il vous faudra toujours trois étapes pour valider vos modifications :

  1. Appliquer les modifications en local (git commit -m "mon message expliquant les modifs")
  2. Essayer de récupérer toutes les dernières modifications du repository (git pull) vers votre code « local », au cas où d’autres personnes auraient « pushé » des modifications
  3. Envoyer vos modifications sur le serveur pour qu’elles soient accessibles à tout le monde (git push)

Django : autorisations sur mesure – custom authorizations

Voici comment créer manuellement des groupes + des autorisations personnalisées :

from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
new_group, created = Group.objects.get_or_create(
    name ='co_branding_trainer')
ct = ContentType.objects.get_for_model(User)
permission_co_branding_trainer = Permission.objects.create(
    codename='co_branding_trainer',
    name='Co-branding trainer', content_type=ct)
new_group.permissions.add(permission_co_branding_trainer)

Une fois cela fait, on a deux possibilités :

  • aller dans le template et se servir de
    {% if perms.app_label.can_do_something %}
    {% endif %}
  • ou modifier le code vue dans le fichier Python. Dans ce cas, on n’a rien dans le template et si la personne connectée n’a pas les droits, la vue fait un forbidden

Comme je préfère la seconde (qui évite d’avoir dans le template des « if » un peu partout), voici comment faire : on crée un mixin qui se chargera de vérifier si, dans la vue, on a les droits : il suffit de chercher PermissionRequiredMixin dans le code de Django, et vous verrez que tout est simple, à partir de là vous pourrez faire descendre vos vues génériques de PermissionRequiredMixin.

Unity

Unity aide-mémoire

GameObjects : mémo

Taille Mesh Mesh m = obj.GetComponent<MeshFilter>().sharedMesh;
Debug.Log(m.bounds.size);
Taille Sprites var m = transform.GetComponent<Renderer>();
Debug.Log(m.bounds.size);

Assets et Objets

Pour comprendre comment gérer correctement les données dans Unity, il faut impérativement comprendre comment Unity identifie et sérialise les données.
Le premier point-clé est de faire la distinction entre les Assets et UnityEngine.Objects.

Un Asset est un fichier sur le disque, stocké dans le dossier Assets d’un projet Unity. Les textures, modèles 3D, ou encore clips audio sont des types d’Assets très courants. Quelques Assets contiennent des données au format natif de Unity, tels que les materiaux. D’autres Assets doivent être convertis en format natif, comme par exemple les fichiers FBX.

UnityEngine.Object, ou bien Object avec un ‘O‘majuscule, est un ensemble de données sérialisées qui décrivent une instance spécifique d’une ressource. Cela peut être n’importe quelle ressource utilisée par le Unity Engine, telle qu’un mesh, sprite, AudioClip ou AnimationClip. Tous les objets sont des sous-classes de la classe de base UnityEngine.Object.

Alors que la plupart des types Object sont built-in (= natifs), il y a deux types spéciaux.

  1. Un ScriptableObject fournit un système pratique pour les développeurs qui désirent définir leurs propre types de données. Ces types peuvent être nativement sérialisés et dé-sérialisés par Unity, et manipulés dans la fenêtre « inspecteur » de l’éditeur de Unity
  2. Un MonoBehaviour fournit un wrapper qui est lié à un MonoScript. Un MonoScript est un type de données interne que Unity utilise pour garder une référence à une class spécifique de script à l’intérieur d’un namespace et assembly spécifique. Un MonoScript ne contient pas réellement de code exécutable.

Il y a une relation one-to-many entre Asset et Object. Dit autrement, un fichier Asset peut contenir un ou plusieurs Object.

– Créer l’URL de logout :
url(r'^logout/$', LogoutView.as_view(), name='logout'),

– Créer la vue LogoutView
from django.contrib.auth import views

class LogoutView(views.LogoutView):

    def __init__(self):
        self.next_page = '/'

<form action="{% url 'login' %}" method="post" accept-charset="utf-8">
    {% csrf_token %}
    {% for field in form %}
        <label>{{ field.label }}</label>
        {% if field.errors %}
            {{ field.errors }}
        {% endif %}
        {{ field }}
    {% endfor %}
    <input type="hidden" name="next" value="{{ next }}" />
    <input class="button small" type="submit" value="Submit"/>
</form>

Références entre Object‘s

Tous les UnityEngine.Objects peuvent avoir des références vers d’autres UnityEngine.Objects. Ces derniers peuvent résider dans le même fichier Asset, ou peuvent être importé à partir d’autres fichiers Asset. Par exemple, un Object matériau a habituellement une ou plusieurs références vers des Object texture. Ces Object texture sont généralement importés à partir d’un ou plusieurs autres fichiers Asset (tels que des PNG‘s ou JPG‘s).
Lorsqu’elles sont sérialisées, ces références sont constituées en deux blocs de données : un File GUID et un Local ID.
Le File GUID identifie le fichier Asset. Un Local ID identifie chaque objet à l’intérieur d’un fichier Asset, car un fichier Asset peut contenir plusieurs objets.
L’identification et le système de référence peut être visualisé dans un éditeur de texte : créez un nouveau projet Unity et changez Editor Settings en cochant Visible Meta Files et Serialiaze Assets as text. Créez un nouveau matérieau, puis importez une texture dans le projet. Assignez le matériau à un cube dans la scène et sauvez cette dernière.
Avec un éditeur de texte, ouvrez le fichier .meta associé au matériau. Une ligne guid sera présente parmi les première lignes. Cette ligne définit le GUID du matériau. Pour trouver l’id local, ouvrez le fichier matériau dans un éditeur de texte. La première ligne qui ressemble à ‘--- !u! &2100000‘ correspond à l’id local.

Pourquoi des « File GUIDs » et des Local ID‘s ?

Pourquoi le principe des « File GUIDs » et des Local ID‘s est-il important  ? La réponse est : dans la stabilité et la possibilité d’avoir un workflow indépendant de la plateforme.

Comment faire un affichage 100% proportionnel quelle que soit la résolution de l’écran ?

En fait, d’après ce que j’ai compris, il faut comprendre qu’on ne « devrait » pas toucher à la taille de la fenêtre de l’application : c’est l’utilisateur qui la fixe : téléphone mobile, portrait ou paysage, ou écran 4k voire 8k, l’application doit s’adapter et devrait même être redimensionnable.
L’idée que j’ai retirée de mon expérience, qui est peut-être un peu différente de la réalité « Unity », est la suivante :

  • Il faut créer un Canvas et lui dire de rester proportionnel à l’écran. Comme cela, même sur les écrans géants, il s’adaptera ;
  • Dans ce Canvas, il faut créer un « référent » qui servira de conteneur à tout le reste. Ce conteneur, il faut lui dire de « remplir » son parent (donc de remplir le Canvas) selon une échelle à

Pourquoi ne pas faire ces deux opérations dans le Canvas ? (1) dire de rester proportionnel + (2) dire de « remplir » le parent Parce que le Canvas connaît et gère le composant Canvas Scaler, et le « référent » (qui sera un Pourquoi ne pas faire ces deux opérations dans le Canvas ? Parce que le Canvas connaît et gère le composant Canvas Scaler et que le « référent », ou « conteneur », connaît et gère le composant Aspect Ratio Fitter)

Cheatsheets

Une « cheatsheet » est une page dans laquelle je mets touts les petites astuces que j’ai récoltées ici ou là, et qui m’ont permises de gagner du temps (ou, à l’inverse, qui m’éviteront d’en reperdre beaucoup, car j’en ai perdu beaucoup et que je ne veux plus en perdre…).

Et les astuces qui suivent, je ne sais pas où les mettre :

Comment imprimer une image sur plusieurs feuilles (facilement) ?

  • Lancez Paint et ouvrez l’image concernée
  • Choisissez : Impression »» Mise en page
  • Sous « échelle », changez le nombre de page en X et en Y
  • Cliquez sur « ok »
  • CTRL-P pour imprimer (assurez-vous bien que « toutes les pages » soit coché)
Commentaires fermés sur Cheatsheets Publié dans Mots-clé

2 – Preonic : le clavier parfait. Pour les nerds.

C’est l’Ergodox Infinity, mais en plus petit.

  1. mkdir keyboards && cd keyboards && mkdir preonic && cd preonic
  2. git clone https://github.com/qmk/qmk_firmware
  3. cp -R ./keyboards/preonic/keymaps/default ./keyboards/preonic/keymaps/azerty
  4. make preonic:azerty:dfu

Vous pourrez ainsi éditer le dossier qui est maintenant votre dossier custom azerty

vim ./keyboards/preonic/keymaps/azerty