leg.ovh
j’ai monté un site web.
je vais essayer de résumer ce que j’ai appris.
voici in fine la structure du dossier. on va procéder dans l’ordre.
.
├── .env
├── .gitignore
├── app.py
├── legovh.sock
├── llm.md
├── todo.md
├── conf/
│ └── gunicorn_config.py
├── crons/
│ ├── log_cleaner.py
│ └── room_cleaner.py
├── data/
│ ├── days.json
│ ├── powers.json
│ └── rooms.json
├── log/
│ ├── access.log
│ ├── error.log
│ └── perso.log
├── models/
│ ├── game_room.py
│ └── model_openai.py
├── routes/
│ ├── horoscope.py
│ └── unomas.py
├── static/
│ ├── favicon.ico
│ ├── css/
│ ├── fonts/
│ ├── img/
│ ├── js/
│ └── svg/
├── templates/
│ ├── horoscope/
│ │ └── ...
│ ├── uno/
│ │ └── ...
│ └── index.html
├── utils/
│ ├── date_formatter.py
│ └── refresh_openai.py
└── venv/
└── ...
racine
on a un fichier .env
pour stocker les secrets,
qu’on appelle avec la librairie
dotenv
.
en soi, je sais pas à quoi ça sert.
je pourrais lire d’un fichier secrets.json
,
pour moi c’est pareil.
la cyber c’est un métier…
ce qui compte, c’est de le foutre dans le
.gitignore
. vite.
fichier ignore dans lequel on va mettre aussi
/venv
, le dossier d’environnement virtuel,
legovh.sock
, le socket nginx,
les logs (m’enfin ça peut se négocier)
et les pycache
précompilés.
on a enfin
todo.md
,
une bête liste mais c’est utile, et
llm.md
pour “décrire le projet à cursor”.
finalement, pas convaincu de llm.md
.
la plupart des modifs sont atomiques ou par fichier,
et on a vite la flemme de le maintenir,
il devient décalé sur le projet…
à voir… surtout avec les :
blueprints
(c’est le fichier routes
, je triche un peu)…
première fois que j’utilisais, bah : pas évident.
certes, c’est plus propre. mais il y a des petits gotchas.
notamment le préfixage dans flask :
return redirect(url_for('unomas.unomas'))
qui peut vite donner lieu à deux syntaxes :
<a href="url_for('unomas.reveal')"></a>
<div hx-get="/unomas/reveal"></div>
ceci dit, c’est plus propre… vraiment ?
j’ai lu un très bon blog : grosso modo, l’idée est d’organiser son code comme un livre.
chapitres, sous-chapitres, sections, sous-sections.
pas de feuilles volantes.
c’est un peu la philosophie de levelsio qui fait du business avec un gros fichier .php.
c’est peut-être mieux pour les llms, et ça va peut-être compter.
autant j’adore trier (y’a qu’à voir ce blog), autant j’adore l’idée.
on verra.
bon ensuite, la conf gunicorn j’en ai déjà parlé, on arrive donc à :
cronjobs
j’aime pas nano, je comprends pas la différence entre crontab et cron normal, bordel, mais ça va.
rien de bien compliqué. je partage mon code pour nettoyer mes logs d’accès, car il faut faire attention à ne pas casser les liens symboliques en manipulant le fichier :
import re
import os
import logging
import tempfile
logging.basicConfig(filename='/home/le/legovh/log/perso.log', level=logging.INFO)
logger = logging.getLogger("log cleaner")
log_file = "/home/le/legovh/log/access.log"
static_pattern = re.compile(r'"GET /static/.*?" 200')
def clean_access_log():
tmp_fd, tmp_path = tempfile.mkstemp()
try:
with os.fdopen(tmp_fd, 'w') as tmp_file, open(log_file, 'r') as input_file:
for line in input_file:
if any([
' 304 ' in line,
'"GET /unomas/get_room_data HTTP/1.0" 200' in line,
static_pattern.search(line),
]):
continue
tmp_file.write(line)
with open(tmp_path, 'r') as tmp_file, open(log_file, 'w') as log:
log.write(tmp_file.read())
os.unlink(tmp_path)
logger.info("Access log cleaned.")
except Exception as e:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
logger.exception(e)
if __name__ == '__main__':
clean_access_log()
le script va filtrer trois choses :
- les requêtes 200 pour le static, pas intéressant.
- les réponses 304 : c’est de la redite, m’en fous.
- certaines adresses spéciales (
/unomas/get_room_data
)
pourquoi ces adresses ?
pour simuler la réactivité, j’utilise la fonction polling de htmx.
pourquoi ? flask supporte pas les sse (server side events), htmx non plus (sans librairies), et puis en soi, ça marche très bien.
mais ça fait beaucoup de requêtes (1 par seconde dans ce cas), donc on filtre.
et si on passait aux sse ?
datastar.dev a l’air d’un très très bon projet.
mais il faudra changer de backend ! peut-être l’heure de passer à fasthtml…
la data !
franchement lire en json c’est fort.
merci à gomakethings pour cette super idée.
(pauvre vieux, il en a bavé cette année, et a pris un job corpo)
pas besoin de redis, rien.
charger et sérialiser un objet c’est pas si long.
(et encore, quand y’a besoin de sérialiser, pour appeler une méthode ou quoi)
et si le fichier devient trop gros, on le découpe.
retenez bien les enfants :
one json a day keeps the doctor away.
logs
pas grand chose à dire, si ce n’est :
tout mettre dans un seul log (perso.log
).
attentionn depuis les cronjobs, le __name__
ça marche pas.
ah, et access.log
c’est édifiant.
le nombre de hacks et de crawler. misère.
surtout du wp-admin
mais quand même.
j’ai pas mis de robots.txt
.
je devrais peut-être.
models
game_room.py
, etc, mes petites classes custom.
model_openai.py
, alors là !
le schéma json c’est vraiment bien (pour économiser sur les tokens d’entrée), mais faut prendre le coup de main.
et pourquoi ça supporte pas les dicts, aucune idée.
m’enfin, voilà comment j’ai fait pour mon horoscope :
from pydantic import BaseModel, Field
from enum import Enum
class SingleHoroscope(BaseModel):
amour: str
travail: str
tonus: str
class ZodiacSign(str, Enum):
BELIER = "Bélier"
TAUREAU = "Taureau"
GEMEAUX = "Gémeaux"
CANCER = "Cancer"
LION = "Lion"
VIERGE = "Vierge"
BALANCE = "Balance"
SCORPION = "Scorpion"
SAGITTAIRE = "Sagittaire"
CAPRICORNE = "Capricorne"
VERSEAU = "Verseau"
POISSONS = "Poissons"
class Horoscopes(BaseModel):
signe: ZodiacSign
horoscope: SingleHoroscope
class Answer(BaseModel):
answer: list[Horoscopes] = Field(description="Liste des horoscopes par signe. Doit contenir les 12 signes du zodiaque.")
voilà, pas un dict mais une liste de tuples (clé, valeur).
on s’amuse bien.
bonjour la date s’il-vous-plaît
mon formateur de date en français :
from datetime import datetime
jours = {
'Monday': 'lundi',
'Tuesday': 'mardi',
'Wednesday': 'mercredi',
'Thursday': 'jeudi',
'Friday': 'vendredi',
'Saturday': 'samedi',
'Sunday': 'dimanche'
}
mois = {
'01': "janvier",
'02': "février",
'03': "mars",
'04': "avril",
'05': "mai",
'06': "juin",
'07': "juillet",
'08': "août",
'09': "septembre",
'10': "octobre",
'11': "novembre",
'12': "décembre"
}
def get_date():
now = datetime.now()
today = now.strftime('%Y-%m-%d')
weekday = now.strftime('%A')
weekday = jours[weekday]
date = today.split("-")
date = [date[2].lstrip('0'), mois[date[1]], date[0]]
date = weekday + " " + " ".join(date)
return date
surely there must be a better way to do this…
pour finir
toujours pas de tailwind. (ceci dit, le nouveau tailwind a l’air bien).
on design mobile first. avec gold.css et du layering ça se fait sans aucun souci.
beaucoup d’animations ceci dit. et des belles fonts.
nginx bon, je m’en sors mais faut pas me demander d’y rester trop longtemps.
pas assez de try/except, beaucoup de trucs peuvent encore planter silencieusement.
0 tests.
j’ai fait des conneries mais voilà. my goal was to get it done and now it is done.
et puis… c’était simple.
on verra quand faudra faire du compliqué.
gérer un game state c’est vraiment le end game.
non, le end game c’est une app mobile.
à moins que ce soit de réinventer le web avec numpy.
en attendant, c’était marrant. je peux vraiment passer une plombe à décorer, réparer, améliorer mon petit bout de jardin.
et je vous souhaite une bonne promenade.