web-dev-qa-db-ja.com

setup.pyを使用してpythonプロジェクトをsystemdサービスとしてインストールする

pythonプロジェクトがあり、python setup.py installのようなものを使用してインストールできるようにしたいので、インストールによって自動的にsystemdサービスが作成されます。

パスまたはインポートを正しく設定している可能性があります。

私の環境:

  • Ubuntu 15.04
  • Python 2.7(py3でも機能させるのは素晴らしいことですが)。

プロジェクトの構造:

+ 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 __name__ == '__main__':
  main()

setup.cfg

[bdist_wheel]
universal=1

create_service.sh(多かれ少なかれ):

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-project.service

[Unit]
Description=Super Description

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

[Install]
WantedBy=multi-user.target

パッケージをインストールすると、次の出力が生成されます。

$ 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'

super-projectファイルは/ 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 __name__ == '__main__':
    sys.exit(
        load_entry_point('super-project==0.0.1', 'console_scripts', 'super-project')()
    )

インストールは成功したようですが、

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

/ 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.

ご覧のとおり、モジュールmainが見つかりません。これが主な問題です。

Code/confを変更するときは、次のようにsuper-project/serviceを削除します。

$ 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

一方:

  • $ super-projectから/usr/local/bin/を実行すると、スクリプトは正常に起動しますが(インポート例外はありません)、構成ファイルを読み取ることができません(相対パスまたは絶対パスの問題が原因と考えられます)。
  • $ super-projectからtop-folderを実行すると(プロジェクトコード/ファイルを含むフォルダー)、スクリプトは完全に実行されます

何が欠けていますか?私は問題が何であるかを探すのに多くの時間を費やしてきました。パッケージはdist-packagesディレクトリに正しくセットアップされているようで、セットアップを実行するとすべてのサービスファイルが正しく作成されます。

from __future__ import absolute_importの使用については読みましたが、それをmain.py(機能しない)に追加する必要があるのか​​、それともプロジェクトのすべてのファイルに追加する必要があるのか​​わかりません。

34
newlog

問題のモジュールがsys.pathにないか、ファイルシステムの権限が原因でアクセスできないため、ImportErrorを取得します。
これは、特定のディストリビューション、グループ、および名前のファイルシステム権限をチェックするスクリプトです。

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)))

テスト;

$ 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
1
Nizam Mohamed

すべてのプロジェクトで異なるアプローチを使用して、私ができる最善の方法は、自分のやり方について簡単に説明することです。
警告:私はLinuxでのみ作業します。ガラスまたは丸い果物が取り付けられた壁の開口部については何も知りません... :)

次のようなスクリプトを作成します
ファイル:scripts/super-project.bash_completion

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

次に、MANIFEST.inにインクルードを追加します

include scripts/super-project.bash_completion

通常、「スーパープロジェクト」ファイル(extなし)は、次のようにエントリポイントです。
ファイル:bin/super-project

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

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

このビン/スーパープロジェクトは、エントリポイントをロードするために使用できると思います。

よろしく。

0
LittleEaster

アプリケーションが他のディレクトリから実行されることを確認してください。これは、現在のディレクトリが開始スクリプトが存在する場所であると想定する古典的なケースのようです。

Systemdとは関係ありません。また、ログインシェルの外部からstartコマンドを実行してみてください(サービスによって.profileがロードされていません)。

0
sorin