Filtre actif, cliquez pour en enlever un tag :

Cliquez sur un ou plusieurs tags pour appliquer un filtre sur la liste des notes de type "Journaux" :

Résultat de la recherche (8 notes) :

Jeudi 17 juillet 2025

Journal du jeudi 17 juillet 2025 à 15:44 #bug, #python, #UnJourPeuxÊtre

Voici comment perdre presque deux heures bêtement !

#!/usr/bin/env python3
import requests
import os

session = requests.Session()
session.headers.update({"Content-Type": "application/json"})

auth_response = session.post(
    "http://localhost:3000/api/v1/auths/signin",
    json={
        "email": "contact+admin@stephane-klein.info",
        "password": os.environ['OPEN_WEBUI_ADMIN_PASSWORD']
    }
)
session.headers.update({
	"Authorization": f'Bearer {auth_response.json()["token"]}'
})

with open("hello_world.py", "r") as f:
    response = session.post(
        "http://localhost:3000/api/v1/pipelines/upload",
        files={
            "file": ("hello_world3.py", f, "text/x-python")
        },
        data={
            "urlIdx": "2"
        }
    )
    print(response.text)

L'API de Open WebUI envoyait cette erreur :

{"detail":[{"type":"missing","loc":["body","urlIdx"],"msg":"Field required","input":null},{"type":"missing","loc":["body","file"],"msg":"Field required","input":null}]}

Mon erreur se situé au niveau de la ligne :

session.headers.update({"Content-Type": "application/json"})

Si je supprime cette ligne, le script fonctionne parfaitement.

Il semble que la fonction session.post() ne configure pas correctement le Content-Type multipart lorsque ce header a été défini au préalable au niveau de la session.

J'ai cherché pendant quelques minutes une issue à ce sujet, sans succès.

Dans l'idéal, requests devrait émettre un warning dans cette situation.

Je viens de créer cette issue : "POST Multipart-Encoding does not work if the Content-Type header has been previously defined in the session"

#UnJourPeuxÊtre je proposerai aussi une implémentation.


D'autre part, ma configuration :

session.headers.update({"Content-Type": "application/json"})

n'avait aucune utilité puisque post définit automatiquement "Content-Type": "application/json" quand on utilise le paramètre json= 🙈.

Mercredi 25 juin 2025

Est-ce qu'une fonction Open WebUI peut importer une autre fonction Open WebUI ? #python

J'ai essayé de comprendre si une fonction Open WebUI pouvait importer le code d'une autre fonction Open WebUI.
La réponse est non. Je vais tenter dans cette note d'expliquer pourquoi.

(j'ai aussi publié une version de cette note en anglais dans la section "discussions" de Open WebUI)

Open WebUI propose de méthode pour créer ou mettre à jour une fonction Open WebUI sur une instance en production : via l'interface web d'administration, ou via l'API REST.

Une instance production fait référence à Open WebUI hébergé sur une Virtual machine ou un Cluster Kubernetes, par opposition à une instance locale lancée en mode développement.

Dans un premier temps, j'ai essayé d'importer dans Open WebUI les deux fichiers suivants :

# utils.py
def add(a, b):
    return a + b
# hello_world.py
from pydantic import BaseModel, Field

from .utils import add

class Pipe:
    class Valves(BaseModel):
        pass

    def __init__(self):
        self.valves = self.Valves()

    def pipe(self, body: dict):
        print("body", body)

        return f"Hello, World! {add(1, 2)}"

Le fichier hello_world.py contient un import de utils.add implémenté dans le premier fichier.

L'importation du premier fichier est refusée par Open WebUI parce que class Pipe: est absent de utils.py.

J'ai ensuite trompé Open WebUI en ajoutant une classe Pipe fictive das le fichier utils.py et l'importation a réussi.

Ensuite l'import de hello_world.py a échoué parce que Open WebUI n'arrive pas a effectué l'import from .utils import add. J'ai ensuite effectué plusieurs tentatives d'import absolut, par exemple from open_webui.utils import add… mais sans succès.

J'ai pris un peu de temps pour étudier l'implémentation d'Open WebUI et j'ai identifié cette section de code :

module_name = f"tool_{tool_id}"
module = types.ModuleType(module_name)
sys.modules[module_name] = module

Ce code permet à Open WebUI de charger dynamiquement le code source des modules qui sont stockés dans la base de données.

Un esprit tordu pourrait en pratique importer une fonction chargé dynamiquement dans un autre module dynamique, par exemple :

from tool_utils import add

Mais cette méthode ne correspond pas à l'usage normal d'Open WebUI.

Pour implémenter des fonctions "modulaires", Open WebUI conseille d'utiliser la fonctionnalité "Pipelines" :

Welcome to Pipelines, an Open WebUI initiative. Pipelines bring modular, customizable workflows to any UI client supporting OpenAI API specs – and much more! Easily extend functionalities, integrate unique logic, and create dynamic workflows with just a few lines of code.

source

Pour les personnes qui souhaitent vraiment effectuer des imports dans des fonctions Open WebUI sans utiliser la fonction Pipelines, il existe tout de même une solution que j'ai implémentée dans la branche test-if-openwebui-function-support-import.

Voici le contenu de /functions/hello_world.py :

from pydantic import BaseModel, Field

from open_webui.shared.utils import add

class Pipe:
    class Valves(BaseModel):
        pass

    def __init__(self):
        self.valves = self.Valves()

    def pipe(self, body: dict):
        print("body", body)

        return f"Hello, World! {add(1, 2)}"

Le contenu de /shared/utils.py

def add(a, b):
    return a + b

Pour rendre accessible /shared/utils.py dans l'instance d'Open WebUI lancé loculement, j'ai configuré de volume mounts suivante dans mon /docker-compose.yml :

  openwebui:
    image: ghcr.io/open-webui/open-webui:0.6.15
    restart: unless-stopped
    volumes:
      - ./shared/:/app/backend/open_webui/shared/
    ports:
      - "3000:8080"

Ensuite, si je souhaite pouvoir déployer en production cette fonction Open WebUI et le module utils.py, il sera nécessaire de build une image Docker customisé d'Open WebUI pour y inclure le fichier /shared/utils.py.

Cette méthode peut fonctionner, mais cela reste un "hack" non conseillé. Il est préférable d'utiliser la méthode "Pipelines".

Vendredi 13 juin 2025

Journal du vendredi 13 juin 2025 à 14:37 #JaiDécouvert, #python

Je viens de découvrir la fonction inspect.cleandoc de la librairie standard de Python.

Exemple :

# foobar.py
from inspect import cleandoc

def foobar(body):
    print(body)


foobar(
    body=cleandoc("""
        My text, with indentation

        - item 1
            - item 1.1
        - item 2

        Last line
    """)
)
$ python foobar.py
My text, with indentation

- item 1
    - item 1.1
- item 2

Last line

Je trouve cela très pratique pour améliorer la lisibilité du code source sans générer des indentations qui ne devraient pas être présentes dans les données.

Mercredi 7 mai 2025

Mercredi 5 février 2025

Journal du mercredi 05 février 2025 à 18:32 #OnMaPartagé, #JaiDécouvert, #python, #package, #mise

Un ami m'a fait découvrir uv (https://github.com/astral-sh/uv).

An extremely fast Python package and project manager, written in Rust.

source

Je trouve cela amusant de constater que Rust prend en charge de plus en plus d'outils pour différents langages 😉.

Le projet a commencé fin 2023.

Voici un thread Hacker News de 200 commentaires à ce sujet qui date de février 2024 : Uv: Python packaging in Rust .

L'article de ce thread contient beaucoup d'éléments intéressants : https://astral.sh/blog/uv

Son nom uv semble être une référence à uvloop.

J'en ai profité pour migrer le playground mise-python-flask-playground de pip vers uv : https://github.com/stephane-klein/mise-python-flask-playground/commit/2f1678798cfc6749dcfdb514a8fe4a3e54739844.

J'ai lancé une installation et effectivement, sa rapidité est très impressionnante :

$ uv pip install -r requirements.txt
Resolved 15 packages in 245ms
Prepared 15 packages in 176ms
Installed 15 packages in 37ms
 + alembic==1.14.1
 + blinker==1.9.0
 + click==8.1.8
 + flask==3.1.0
 + flask-migrate==4.1.0
 + flask-sqlalchemy==3.1.1
 + greenlet==3.1.1
 + itsdangerous==2.2.0
 + jinja2==3.1.5
 + mako==1.3.9
 + markupsafe==3.0.2
 + psycopg2-binary==2.9.10
 + sqlalchemy==2.0.37
 + typing-extensions==4.12.2
 + werkzeug==3.1.3

uv ne propose pas seulement une amélioration de l'installation de packages Python, mais propose beaucoup d'autres choses comme :

Pour cette partie, dans un but d'unification, je continuerai à utiliser Mise pour installer une version précise de Python. De plus, Mise intègre nativement UV : https://mise.jdx.dev/mise-cookbook/python.html#mise-uv

Exemple :

$ uv run example.py

Je pense avoir compris que cela lance ce script avec les dépendances du virtual environment du projet. Un peu comme fonctionne npm, yarn ou pnpm qui permet aux scripts d'utiliser les packages présents dans ./node_modules/.

Par exemple, le linter Python ruff, exemple :

$ uv tool run ruff

J'ai un peu parcouru la documentation de pyproject.toml : https://packaging.python.org/en/latest/guides/writing-pyproject-toml/.

J'ai lu aussi la section uv - Locking environments.

Suite à ces lectures, j'ai migré le playground mise-python-flask-playground vers pyproject.toml : https://github.com/stephane-klein/mise-python-flask-playground/commit/c17216464778df4bc00bf782d5a889cb3f198051.

Je ne suis pas certain que ces commandes soient une bonne pratique :

$ uv pip compile requirements.in -o requirements.txt
$ uv pip install -r requirements.txt

Playground qui présente comment je setup un projet Python Flask en 2025 #dev-kit, #python, #mise, #docker, #WSL, #playground, #software-engineering

Je pense que cela doit faire depuis 2015 que je n'ai pas développé une application en Python Flask !

Entre 2008 et 2015, j'ai beaucoup itéré dans mes méthodes d'installation et de setup de mes environnements de développement Python.

D'après mes souvenirs, si je devais dresser la liste des différentes étapes, ça donnerai ceci :

  • 2006 : aucune méthode, j'installe Python 🙂
  • 2007 : je me bats avec setuptools et distutils (mais ça va, c'était plus mature que ce que je pouvais trouver dans le monde PHP qui n'avait pas encore imaginé composer)
  • 2008 : je trouve la paie avec virtualenv
  • 2010 : j'ai peur d'écrire des scripts en Bash alors à la place, j'écris un script bootstrap.py dans lequel j'essaie d'automatiser au maximum l'installation du projet
  • 2012 : je me bats avec buildout pour essayer d'automatiser des éléments d'installation. Avec le recul, je réalise que je n'ai jamais rien compris à buildout
  • 2012 : j'utilise Vagrant pour fixer les éléments d'installation, je suis plutôt satisfait
  • 2015 : je suis radicale, j'enferme tout l'environnement de dev Python dans un container de développement, je monte un path volume pour exposer le code source du projet dans le container. Je bricole en entrypoint avec la commande "sleep".

Des choses ont changé depuis 2015.

Mais, une chose que je n'ai pas changée, c'est que je continue à suivre le modèle The Twelve-Factors App et je continue à déployer tous mes projets packagé dans des images Docker. Généralement avec un simple docker-compose.yml sur le serveur, ou alors Kubernetes pour des projets de plus grande envergure… mais cela ne m'arrive jamais en pratique, je travaille toujours sur des petits projets.

Choses qui ont changé : depuis fin 2018, j'ai décidé de ne plus utiliser Docker dans mes environnements de développement pour les projets codés en NodeJS, Golang, Python

Au départ, cela a commencé par uniquement les projets en NodeJS pour des raisons de performance.

J'ai ensuite découvert Asdf et plus récemment Mise. À partir de cela, tout est devenu plus facilement pour moi.
Avec Asdf, je n'ai plus besoin "d'enfermer" mes projets dans des containers Docker pour fixer l'environnement de développement, les versions…

Cette introduction est un peu longue, je n'ai pas abordé le sujet principal de cette note 🙂.

Je viens de publier un playground d'un exemple de projet minimaliste Python Flask suivant mes pratiques de 2025.

Voici son repository : mise-python-flask-playground

Ce playground est "propulsé" par Docker et Mise.

J'ai documenté la méthode d'installation pour :

Je précise que je n'ai pas eu l'occasion de tester l'installation sous Windows, hier j'ai essayé, mais je n'ai pas réussi à installer WSL2 sous Windows dans un Virtualbox lancé sous Fedora. Je suis à la recherche d'une personne pour tester si mes instructions d'installation sont valides ou non.

Briques technologiques présentes dans le playground :

Voici quelques petites subtilités.

Dans le fichier alembic.ini j'ai modifié le paramètre file_template parce que j'aime que les fichiers de migration soient classés par ordre chronologique :

[alembic]
# template used to generate migration files
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s

Ce qui donne par exemple :

20250205_124639_users.py
20250205_125437_add_user_lastname.py

Ici le port de PostgreSQL est généré dynamiquement par docker compose :

  postgres:
    image: postgres:17
	...
	ports:
      - 5432 # <= ici

Avec cela, fini les conflits de port quand je lance plusieurs projets en même temps sur ma workstation.

L'URL vers le serveur PostgreSQL est générée dynamiquement par le script get_postgres_url.sh qui est appelé par le fichier .envrc. Tout cela se passe de manière transparente.

J'initialise ici les extensions PostgreSQL :

def init_db():
    db.drop_all()
    db.session.execute(db.text('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'))
    db.session.execute(db.text('CREATE EXTENSION IF NOT EXISTS "unaccent"'))
    db.session.commit()
    db.create_all()

et ici dans la première migration :

def upgrade():
    op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
    op.execute('CREATE EXTENSION IF NOT EXISTS "unaccent";')
    op.create_table('users',
        sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
        sa.Column('firstname', sa.String(), nullable=False),
        sa.PrimaryKeyConstraint('id')
    )

Lundi 20 mai 2024

Journal du lundi 20 mai 2024 à 15:13 #coding, #jupyter, #python, #javascript

Les deux fois où j'ai essayé d'utiliser Jupyter pour réaliser, par exemple, une calculatrice financière, j'ai fini par constater que je ne trouve pas cet outil pratique. Après quelques heures, je retourne soit à un script Python classique, soit à la création d'une page web basée sur HTML et JavaScript, qui me donne bien plus de flexibilité que Jupyter.

Il est possible que ce soit parce que je connais mal Jupyter, mais j'y ai tout de même consacré plus de deux heures hier soir, explorant notamment les Jupyter Widgets (ipywidgets).

Lundi 25 mars 2024

Journal du lundi 25 mars 2024 à 20:00 #opinion, #coding, #python, #javascript

Après 11 mois de procrastination, j'ai enfin pris le temps de faire ce script https://github.com/stephane-klein/toggl-report-helper-script

Au départ, j'hésitais entre une implémentation en Python ou en JavaScript. J'ai finalement opté pour JavaScript, car cela correspond à ma nouvelle doctrine.

Même si Python reste le langage que j'affectionne profondément, que je trouve toujours aussi élégant, j'ai pris la décision de me consacrer pleinement à JavaScript.

Fin de la liste des notes.