Dépendances python des plugins: venv, pyenv & debian 12

Bonjour,

On a pas mal travaillé avec @TiTidom ces dernières semaines/mois afin de mettre au point un moyen simple d’installer les dépendances python de nos plugins et donc comme promis sur quelques sujets, voici une proposition pour faciliter ces installs:

objectifs en quelques mots:

  • dans un venv pour la compatibilité avec debian 12 (mais pas que, même sous debian 10 & 11 c’est un vrai plus car plus d’impact cross plugins sur des dépendances partagées puisqu’elles ne le sont plus)
  • avec l’aide de pyenv si besoin d’une version de python pas encore supportée de base par Debian et donc plus de soucis avec de vieille installation debian10 qui bloque les évolutions, c’est machine là vont pouvoir tourner sous python 3.12 si on veut;
  • tout en optimisant tout cela pour éviter d’installer 5 fois python pour 5 plugins différents

Et voici donc le résultat qui se repose sur la lib mise au point par @nebz qui est à elle seule un vrai plus (avec auto-correction des erreurs les plus fréquentes etc, je vous laisse lire la doc mais ce n’est pas le sujet du jour)

Actuellement cette nouvelle lib est encore dans un fork sur mon compte mais on va probablement centraliser tout ça

En quelques mots voici comment cela fonctionne, pour plus de détails voir la doc.
Il faut avoir un fichier requirements.txt qui contient la liste des dépendances requises & le script d’install habituel install_apt.sh, ces deux fichiers se trouvant à priori dans votre dossier resources.

Contenu du fichier install_apt.sh qui devrait convenir dans la plupart des cas:

######################### INCLUSION LIB ##########################
BASE_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
wget https://raw.githubusercontent.com/Mips2648/dependance.lib/master/dependance.lib --no-cache -O ${BASE_DIR}/dependance.lib &>/dev/null
PLUGIN=$(basename "$(realpath ${BASE_DIR}/..)")
LANG_DEP=en
. ${BASE_DIR}/dependance.lib
##################################################################
wget https://raw.githubusercontent.com/Mips2648/dependance.lib/master/pyenv.lib --no-cache -O ${BASE_DIR}/pyenv.lib &>/dev/null
. ${BASE_DIR}/pyenv.lib
##################################################################

# TARGET_PYTHON_VERSION="3.11"
# VENV_DIR=${BASE_DIR}/venv
# APT_PACKAGES="first1 second2"

launchInstall

ce qu’on y trouve:

  • chargement des libs: dependance.lib & pyenv.lib
  • en option:
    • spécifier la version minimum de python voulue
    • le dossier venv si on veut un dossier différent de resources/venv
    • une liste de paquet apt à installer (séparés par un espace); aucun rapport avec les dépendances python mais ca permet de couvrir des besoins d’installation assez fréquent en plus des dépendances python
  • appel à la fonction launchInstall

et le boulot sera fait, rien de plus n’est nécessaire :

  • création ou mise à jour d’un venv avec une version python >= à la version demandée (ou la version du système si non précisé)
  • installation des dépendances listées dans requirements.txt

Bien sur si vous utilisez ce système pour l’installation des dépendances, cela veut dire que vous n’utilisez pas le système « packages.json » du core et que donc il faut rajouter également quelques lignes à votre class eqLogic pour le dependancy_info() et dependancy_install() (je peux proposer des snipets tout fait pour ca)


Pour votre info, j’ai également fait une proposition pour la gestion des venv via le core: Pip on debian 12 by zoic21 · Pull Request #2566 · jeedom/core · GitHub et cela surtout pour offrir la compatibilité avec debian12

les différences avec avantages/inconvénient que je vois entre les 2 options (uniquement sur la partie python bien entendu, le core gère plus que cela) :

packages.json:

  • (+) intégré complétement au core, encore moins de boulot à faire que ce qui est présenté ici.
  • (-) pas de dependabot possible (en tout cas pas facilement)
  • (-) ne gère les venv qu’à partir de debian12 (choix fait pour pouvoir assurer la retro-compatibilité à 100% sur les versions antérieur)
  • (-) ne gère pas (encore) l’installation de versions de python différentes via pyenv (mais si l’équipe accepte ca sera possible, j’ai déjà ce qu’il faut sous le coude)

pyenv.lib

  • (-) un peu plus de boulot (dependancy_info() et dependancy_install() à écrire + requirements.txt & install_apt.sh)
  • (+) facile d’avoir un dependabot qui tourne sur github sur votre repo pour la gestion de vos dépendances puisqu’on utilise le fichier requirements.txt qui est un standard python
  • (+) gère les venv sur toutes versions de debian (>=10) dès aujourd’hui et vous gérez la transition!
  • (+) possible d’utiliser n’importe quelle version de python sur n’importe quelle version de Debian sans le moindre impact en dehors de votre plugin!
  • (+) parce que cette lib se base sur dependance.lib de @nebz on bénéficie de tout ces avantages: log bien formaté et clair (c’est subjectif c’est vrai) avec mise en évidence des erreurs avec surtout la correction automatique des erreurs les plus fréquentes rencontrées lors des installations!
    donc moins de posts sur community pour ce genre de problème; et ça, ça n’a pas de prix ! :wink:

edit:
J’avais oublié de préciser et pourtant c’est très important, il faut ajouter cette fonction dans la class eqLogic pour exclure le dossier venv du backup, sinon ca augmente beaucoup la taille du backup jeedom:

	public static function backupExclude() {
		return [
			'resources/venv'
		];
	}
14 « J'aime »

Merci pour le travail fourni.

Les questions qui me viennent suite à la lecture de ce post sont:
Quel est l’avis de Jeedom là dessus ?
Il est pourtant un des principal concerné par les daemons python qui fuient et pas une réaction, consigne directive !
Que vont-ils faire dans la version actuelle pour corriger ?
Vont-ils s’aligner sur cette solution ?

Je suis du même avis que @jpty car quand je vois ce sujet sur l atlas
Se serait bien d’avoir leur avis aussi

Je pense que c’est hors scope de jeedom (ils ne contrôlent pas la version de python) et cette histoire de fuite n’est pas non plus généralisée sur tous les démons python (depuis que j’ai vu les sujets je monitore tous mes plugins et pas un ne bouge plus que de raison, voir pas du tout, alors que sur rfxcom j’avais déjà vu le problème) donc perso je continue de croire que le problème est aussi lié aux libs utilisées et pas qu’à la version de python.

Toujours est-il qu’independament de tout ca, en tant que dev tiers on reste libre d’implémenter la solution que l’on souhaite, je ne fais qu’utiliser les possibilités mise à disposition par le core.

1 « J'aime »

Merci pour le dev.

De mémoire, install_apt.sh est la méthode qui ne prend pas packages.json en compte, c’est bien ça ?
Avec packages.json, il est possible de faire des scripts preinstall et postinstall qui pourrait contenir ce qui est nécessaire. Vous avez pensé à cette possibilité ?

Oui, c’est ce que je précise d’ailleurs

oui bien sur, et testé également mais le résultat n’est pas satisfaisant et cela n’apporte pas de réel avantage alors que ca complexifie le tout.

  • log moins clair et je ne suis pas arrivé à faire fonctionner correctement la lib de Nebz
  • compliqué voir impossible de vérifier l’état des dépendances si installé par un autre script alors qu’on utilise packages.json

=> soit on utilise le packages.json et on s’embête pas avec un script soit tant qu’à avoir un script on n’a pas besoin du packages.json

C’est un autre sujet, mais il me semblait que la méthode packages.json allait supplanter install_apt.sh à terme. C’est d’ailleurs pour ça que je me suis permis de poser la question.
Ce compromis est une sorte de retour en arrière ou pas du tout ? La nouvelle méthode d’installation conseillée est bien celle avec packages.json ? C’est un compromis provisoire ? (je cherche juste à comprendre)

Je ne décide pas le roadmap de jeedom ni de savoir si cela va disparaître.
Je n’ai pas lu de plan définitif ni ferme à ce sujet.

Je ne doute pas que le jour où cela sera sur la table on pourra contribuer à cette discussion afin d’apporter les solutions adaptées.

Et vu le nombre de plugin c’est pas demain la veille que cela sera d’application.

Étant donné que pour l’instant la solution de packages.json ne répond pas à certains besoins, je ne vois pas de problème à avoir différentes solutions afin que chacun puisse choisir celle la plus adaptée à son cas.

Le packages.json a de réels avantages et si tu relis mon premier post tu verras que j’explique avoir déjà proposé les évolutions du système du core pour gérer les pyenv également.
Mais il a aussi quelques désavantages.

Encore une fois, tout ceci n’est que mon avis et ma proposition. Cela n’engage en rien jeedom.

2 « J'aime »

OK, je comprends mieux ta position.

Ce point est une évidence :smiley:

Bonjour @Mips ,

Dans le cadre du développement de mon nouveau plugin (SomfyUnified), je souhaite qu’il soit, à la fois, compatible Debian 12 mais aussi utilisable dans une version Jeedom actuelle et donc sous Debian 10 ou 11.

Bien évidement, les principes énoncés ici sont au coeur des préoccupations auxquelles je fais face et je tiens avant tout à vous remercier, toi et les personnes citées (@nebz, @TiTdom) pour le travail que vous avez réalisé et la communication associée.

Je suis assez favorablement séduit par l’approche pyenv.lib qui, il me semble, promet une meilleure adaptation aux tournants déja pris pour la gestion des ressources Python et environnements virtuels.

Pour créer mon environnement virtuel Python, je pense que je vais donc implémenter cette lib que vous proposez et je serais donc interessé par des explications sur ce qu’il faut faire au niveau des dependancy_info() et dependancy_install() dont tu parles.

Merci d’avance pour tes éclairages et recommandations.

Salut,

merci pour le retour
je n’ai plus le temps de détailler aujourd’hui mais je te fais ca demain matin sans faute!

et tant que j’y suis, si ca t’intéresse j’ai mis au point ceci pour aider au dev d’un démon python: jeedomdaemon · PyPI dont il y a un exemple ici GitHub - Mips2648/jeedom-aiodemo (ce plugin là n’utilise pas pyenv.lib) mais aussi mes autres plugins (kroomba par exemple mais qui est probablement plus complexe à lire)
je préviens tout de suite, même si cette dernière lib est écrite avec asyncio: cela n’impose pas d’utiliser asyncio (async/await) dans ton code mais ca le permet

1 « J'aime »

Merci de ton retour,

J’ai effectivement prévu d’utiliser asyncio pour le développement du Daemon du plugin.
Asyncio est une réelle amélioration Python je trouve.
Je regarderais également ton travail mais dans un second temps. Je ne me suis pas encore plongé dans le Daemon.
Mes mises à jours sont basées sur les crons pour le moment, ce n’est pas performant mais ca update les valeurs régulièrement en attendant.

des crons qui appellent un script python du coup? sinon je ne comprend pas trop l’interêt sur tout ça :slight_smile:
et si c’est juste l’appel à un script via cron, asyncio ne va rien apporter mais je n’ai p-e pas bien compris


pour revenir à la question sur les modifs à avoir dans le plugin, entre autre sur dependancy_info() et dependancy_install(), en fait c’est déjà documenté ici:

https://doc.jeedom.com/fr_FR/dev/daemon_plugin#La%20méthode%20par%20procédures

  • mettre à jour plugin_info/info.json
  • dependancy_install() peut-être repris tel quel de la documentation, bien sur ne pas suivre l’explication pour install_apt.sh puisque c’est l’objet de ce post
  • dependancy_info() tu peux t’inspirer de ce qu’il y a dans la doc, c’est ce que je faisais avant mais c’est fastidieux car il faut adapter la liste des dépendances pour chaque plugin;

Désormais ma fonction dependancy_info() est celle-ci (quasi toujours):

    public static function dependancy_info() {
        $return = array();
        $return['log'] = log::getPathToLog(__CLASS__ . '_update');
        $return['progress_file'] = jeedom::getTmpFolder(__CLASS__) . '/dependance';
        $return['state'] = 'ok';
        if (file_exists(jeedom::getTmpFolder(__CLASS__) . '/dependance')) {
            $return['state'] = 'in_progress';
        } elseif (!file_exists(self::PYTHON_PATH)) {
            $return['state'] = 'nok';
        } elseif (!self::pythonRequirementsInstalled(self::PYTHON_PATH, __DIR__ . '/../../resources/requirements.txt')) {
            $return['state'] = 'nok';
        }
        return $return;
    }

dans la class je déclare PYTHON_PATH pour utiliser mon venv (c’est juste pour ne pas devoir modifier à 4 endroits le path au cas où)

const PYTHON_PATH = __DIR__ . '/../../resources/venv/bin/python3';

et ma fonction pythonRequirementsInstalled() est celle-ci:

	private static function pythonRequirementsInstalled(string $pythonPath, string $requirementsPath) {
		if (!file_exists($pythonPath) || !file_exists($requirementsPath)) {
			return false;
		}
		exec("{$pythonPath} -m pip freeze", $packages_installed);
		$packages = join("||", $packages_installed);
		exec("cat {$requirementsPath}", $packages_needed);
		foreach ($packages_needed as $line) {
			if (preg_match('/([^\s]+)[\s]*([>=~]=)[\s]*([\d+\.?]+)$/', $line, $need) === 1) {
				if (preg_match('/' . $need[1] . '==([\d+\.?]+)/', $packages, $install) === 1) {
					if ($need[2] == '==' && $need[3] != $install[1]) {
						return false;
					} elseif (version_compare($need[3], $install[1], '>')) {
						return false;
					}
				} else {
					return false;
				}
			}
		}
		return true;
	}

elle permet de vérifier que les dépendances d’un fichier requirements.txt sont bien installées dans le venv (avec la version voulue);
attention je ne gère pas toutes les syntaxes possibles mais juste >=, ~= et ==

cette fonction est aussi dispo dans GitHub - Mips2648/jeedom-tools: Tools and helper class for plugin development (parce que je déteste faire du copier/coller)

last but not least, très important, ajouter cette fonction dans la class eqLogic pour exclure le dossier venv du backup, sinon ca augmente beaucoup la taille du backup jeedom:

	public static function backupExclude() {
		return [
			'resources/venv'
		];
	}
1 « J'aime »

Bonjour @Mips ,

Et merci infiniment pour ton message et toutes ces informations.

Pour ton info, mon plugin fonctionne pour moitié avec des scripts Python et donc la mise en place de cet environnement virtuel est vraiment importante.

J’ai modifié mon code, implémenté la lib [Dépendances python] et j’obtiens bien l’installation de mon Environement Virtuel.

Je n’utilise pas le répertoire resources que je réserverais au Daemon.

J’ai placé mes scripts Python et mon py_venv dans un répertoire dont le chemin est :

plugins/SomfyUnified/core/python/

D’après toi, est-ce normal/attendu que le répertoire py_venv apparaisse vérouillé dans mon explorateur Jeedom ?
image_2024-05-17_163341881

Salut,

Oui c’est normal, cela ne porte pas à conséquence en principe (mais je n’ai jamais installé dans core/

Penses bien à ajouter ce dossier là du coup dans les exclusions du backup

Oui bien sur, c’est ce que j’ai fait dans la fonction d’exclusion pour le backup.

Le verrou ? Je me posais la question car pour le moment, j’ai une erreur d’import sous Python que je ne m’explique pas. :thinking:
Je vais chercher.

tu peux rétablir les droits via le menu config de jeedom

EDIT

  1. Mon essai précédent était sous Debian 12 avec une Venv demandé Python 3.11
    L’install semble s’être faite correctement.
    Le verrou a disparu de lui-même …

  2. Par contre sous un Jeedom 4.4.5 Debian 10, l’installation ne va pas jusqu’au bout, ca relance l’installation sans aboutir.
    image_2024-05-17_170746735
    Une idée ?

Salut,

Si ça se relance sans arriver au bout, peut-être une piste à regarder dans le info.json du plugin, voir si le temps max d’installation des dépendances est positionné assez haut, car l’install de python peut être très longue.

Sur mes plugins qui utilisent cette méthode d’installation, je le met à 60 min pour être sûr que cela s’installe chez tout le monde quel que soit la puissance de la machine derrière :

"maxDependancyInstallTime" : 60,

TiTidom.

1 « J'aime »

Bonjour,

Merci de ton retour.
Pour le moment, il est à 10mn. Il me semble que ca se relance plus fréquement !
Mais je vais passer à 60 mn pour voir.