web-dev-qa-db-fra.com

Utilisation de setup.py pour installer un projet python en tant que service systemd

J'ai un projet Python et je veux pouvoir l'installer en utilisant quelque chose comme python setup.py install afin que l'installation crée automatiquement un service systemd.

J'ai quelques problèmes, très probablement définir les chemins ou les importations correctement.

Mon environnement:

  • Ubuntu 15.04
  • Python 2.7 (bien que ce serait bien de le faire fonctionner aussi dans py3).

Structure du projet:

+ top-folder
  + super_project
    + folder1
      __init__.py
      file1.py
    + folder2
      __init__.py
      file2.py
    __init__.py
    main.py
  setup.py
  setup.cfg

setup.py:

from setuptools.command.install import install
from setuptools import setup, find_packages
import subprocess
import os


class CustomInstallCommand(install):

  def run(self):
    install.run(self)
    current_dir_path = os.path.dirname(os.path.realpath(__file__))
    create_service_script_path = os.path.join(current_dir_path, 'super_project', 'install_scripts', 'create_service.sh')
    subprocess.check_output([create_service_script_path])

setup(
  name='super-project',
  author='Myself',
  version='0.0.1',
  description='My Description',
  packages=find_packages(exclude=['contrib', 'docs']),
  # this will create the /usr/local/bin/super-project entrypoint script
  entry_points={
    'console_scripts': [
      'super-project = super_project.main:main'
    ]
  },
  cmdclass={'install': CustomInstallCommand}
)

main.py

from super_project.folder1.file1 import Class1
from super_project.folder2.file2 import Class2
import logging


def main():
  logging.info('Executing super-project...')
  (...)
  logging.info('super-project execution finished.')

if __== '__main__':
  main()

setup.cfg

[bdist_wheel]
universal=1

create_service.sh (plus ou moins):

SYSTEMD_SCRIPT_DIR=$( cd  $(dirname "${BASH_SOURCE:=$0}") && pwd)
cp -f "$SYSTEMD_SCRIPT_DIR/super-project.service" /lib/systemd/system
chown root:root /lib/systemd/system/super-project.service

systemctl daemon-reload
systemctl enable super-project.service

super-projet.service

[Unit]
Description=Super Description

[Service]
Type=simple
ExecStart=/usr/local/bin/super-service
Restart=always

[Install]
WantedBy=multi-user.target

L'installation du package génère la sortie suivante:

$ Sudo python setup.py install --record files.txt
running install
running build
running build_py
copying super_project/main.py - build/lib.linux-x86_64-2.7/super_project
running install_lib
copying build/lib.linux-x86_64-2.7/super_project/__init__.py - /usr/local/lib/python2.7/dist-packages/super_project
copying build/lib.linux-x86_64-2.7/super_project/main.py - /usr/local/lib/python2.7/dist-packages/super_project
copying build/lib.linux-x86_64-2.7/super_project/db/__init__.py - /usr/local/lib/python2.7/dist-packages/super_project/db
copying build/lib.linux-x86_64-2.7/super_project/db/db_gateway.py - /usr/local/lib/python2.7/dist-packages/super_project/db
(...)
byte-compiling /usr/local/lib/python2.7/dist-packages/super_project/__init__.py to
__init__.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/super_project/main.py to
main.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/super_project/db/__init__.py to
__init__.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/super_project/db/db_gateway.py
to db_gateway.pyc
(...)
running install_Egg_info
running Egg_info
writing requirements to super_project.Egg-info/requires.txt
writing super_project.Egg-info/PKG-INFO
writing top-level names to super_project.Egg-info/top_level.txt
writing dependency_links to super_project.Egg-info/dependency_links.txt
writing entry points to super_project.Egg-info/entry_points.txt
reading manifest file 'super_project.Egg-info/SOURCES.txt'
writing manifest file 'super_project.Egg-info/SOURCES.txt'
Copying super_project.Egg-info to /usr/local/lib/python2.7/dist-packages/super_project-0.0.1.Egg-info
running install_scripts
Installing ai-scenario-qa script to /usr/local/bin
writing list of installed files to 'files.txt'

Le fichier super-project est créé dans/usr/local/bin:

#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'super-project==0.0.1','console_scripts','super-project'
__requires__ = 'super-project==0.0.1'
import sys
from pkg_resources import load_entry_point

if __== '__main__':
    sys.exit(
        load_entry_point('super-project==0.0.1', 'console_scripts', 'super-project')()
    )

L'installation semble réussie, bien que:

$ systemctl status super-project.service
● super-project.service
   Loaded: not-found (Reason: No such file or directory)
   Active: inactive (dead)

L'erreur que je peux voir dans/var/log/syslog:

 Feb 16 20:48:34  systemd[1]: Starting  Super Description...
 Feb 16 20:48:34  super-project[22517]: Traceback (most recent call last):
 Feb 16 20:48:34  super-project[22517]: File "/usr/local/bin/super-project", line 9, in <module
 Feb 16 20:48:34  super-project[22517]: load_entry_point('super-project==0.0.1', 'console_scripts', 'super-project')()
 Feb 16 20:48:34  super-project[22517]: File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 521, in load_entry_point
 Feb 16 20:48:34  super-project[22517]: return get_distribution(dist).load_entry_point(group, name)
 Feb 16 20:48:34  super-project[22517]: File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2632, in load_entry_point
 Feb 16 20:48:34  super-project[22517]: return ep.load()
 Feb 16 20:48:34  super-project[22517]: File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2312, in load
 Feb 16 20:48:34  super-project[22517]: return self.resolve()
 Feb 16 20:48:34  super-project[22517]: File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2318, in resolve
 Feb 16 20:48:34  super-project[22517]: module = __import__(self.module_name, fromlist=['__name__'], level=0)
 Feb 16 20:48:34  super-project[22517]: ImportError: No module named main
 Feb 16 20:48:34  systemd[1]: super-project.service: main process exited, code=exited, status=1/FLURE
 Feb 16 20:48:34  systemd[1]: Unit super-project.service entered fled state.
 Feb 16 20:48:34  systemd[1]: super-project.service failed.
 Feb 16 20:48:34  systemd[1]: super-project.service holdoff time over, scheduling restart.
 Feb 16 20:48:34  systemd[1]: start request repeated too quickly for super-project.service
 Feb 16 20:48:34  systemd[1]: Failed to start Super Description.
 Feb 16 20:48:34  systemd[1]: Unit super-project.service entered fled state.
 Feb 16 20:48:34  systemd[1]: super-project.service failed.

Comme on peut le constater, le module main est introuvable . C'est le problème majeur.

Lorsque je change de code/conf, je supprime le super-projet/service comme suit:

$ Sudo systemctl disable super-project.service
$ Sudo rm -f /lib/systemd/system/super-project.service
$ Sudo systemctl daemon-reload
$ su
# cat files.txt | xargs rm -r

D'autre part:

  • Si j'exécute $ super-project à partir de /usr/local/bin/, le script démarre correctement (aucune exception d'importation), mais les fichiers de configuration ne peuvent pas être lus (très probablement à cause de problèmes de chemin d'accès relatif/absolu).
  • Si j'exécute $ super-project à partir de top-folder (dossier contenant le code/les fichiers du projet), le script fonctionne parfaitement

Qu'est-ce que je rate? J'ai passé beaucoup de temps à chercher quel était le problème. Il semble que le package soit correctement configuré dans le répertoire dist-packages et que tous les fichiers de service sont correctement créés une fois la configuration exécutée.

J'ai lu des articles sur l'utilisation de from __future__ import absolute_import, mais je ne suis pas sûr de devoir l'ajouter à mon main.py (cela ne fonctionne pas) ou à tous les fichiers de mon projet.

24
newlog

Assurez-vous que votre application peut être exécutée à partir d'autres répertoires. Cela semble être un cas classique dans lequel vous supposez que le répertoire en cours est l'emplacement du script de démarrage.

Cela n'a rien à voir avec systemd. Essayez également d’exécuter la commande de démarrage en dehors de votre shell de connexion (votre fichier .profile n’est pas chargé par les services).

1
sorin

En utilisant une approche différente pour tous mes projets, le mieux que je puisse faire est de décrire brièvement comment je le fais.
Attention: je travaille exclusivement sur linux, je ne connais rien aux ouvertures dans les murs munies de verre ou de fruits ronds ... :)

Créer un script comme
fichier : scripts/super-project.bash_completion 

eval "$(register-python-argcomplete super-project)"

Ajoutez ensuite une inclusion dans le fichier MANIFEST.in 

include scripts/super-project.bash_completion

généralement le fichier "super-projet" (sans extension) est un point d’entrée de la manière suivante:
fichier : bin/super-projet 

#!/usr/bin/env python2.7
from super_project.main import main

if __== '__main__':
    main()
else:
    raise NotImplementedError

Je suppose que ce bin/super-projet peut être utilisé pour charger le point d’entrée.

Cordialement.

0
LittleEaster

Vous obtenez une ImportError, car le module en question n'est ni dans sys.path ni accessible, en raison de certaines autorisations du système de fichiers.
Voici un script permettant de vérifier les autorisations du système de fichiers d’une distribution, d’un groupe et d’un nom donnés. 

chk_perm.py

from pkg_resources import get_distribution
import os
import sys

dist, group, name = sys.argv[1:]
dist = get_distribution(dist)
location = dist.location
einfo = dist.get_entry_info(group, name)
if not einfo:
    print('No such group "{}" or name "{}"'.format(group, name))
    sys.exit(1)
m_name = einfo.module_name
path = format(os.path.join(location, *m_name.split('.')))
path = path if os.access(path, os.F_OK) else '{}.py'.format(path)
print('If path "{}" exists: {}'.format(path, os.access(path, os.F_OK) if path.endswith('.py') else True))
print('If path "{}" readable: {}'.format(path, os.access(path, os.R_OK)))

Tester;

$ python chk_perm.py setuptools console_scripts easy_install
If path "lib/python2.7/site-packages/setuptools/command/easy_install.py" exists: True
If path "lib/python2.7/site-packages/setuptools/command/easy_install.py" readable: True

$ foo
Traceback (most recent call last):
  File "bin/foo", line 9, in <module>
    load_entry_point('mypkg==0.0.4', 'console_scripts', 'foo')()
  File "lib/python2.7/site-packages/pkg_resources/__init__.py", line 549, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "lib/python2.7/site-packages/pkg_resources/__init__.py", line 2542, in load_entry_point
    return ep.load()
  File "lib/python2.7/site-packages/pkg_resources/__init__.py", line 2202, in load
    return self.resolve()
  File "lib/python2.7/site-packages/pkg_resources/__init__.py", line 2208, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
ImportError: No module named main

$ python chk_perm.py mypkg console_scripts foo
If path "lib/python2.7/site-packages/pkg/main.py" exists: True
If path "lib/python2.7/site-packages/pkg/main.py" readable: False

$ ls -l lib/python2.7/site-packages/pkg/main.py 
-rw-rw---- 1 root root 104 Mar  6 22:52 lib/python2.7/site-packages/pkg/main.py

$ Sudo chmod o+r lib/python2.7/site-packages/pkg/main.py
$ ls -l lib/python2.7/site-packages/pkg/main.py 
-rw-rw-r-- 1 root root 104 Mar  6 22:52 lib/python2.7/site-packages/pkg/main.py

$ python chk_perm.py mypkg console_scripts foo
If path "lib/python2.7/site-packages/pkg/main.py" exists: True
If path "lib/python2.7/site-packages/pkg/main.py" readable: True

$ foo
App is running
0
Nizam Mohamed