web-dev-qa-db-ja.com

virtualenvの使用中にPythonService.exeを使用してpython service

Python Windows Service using Python3.4。pywin32のwin32serviceモジュールを使用してサービスをセットアップする必要があるWindows7環境がありますほとんどのフックは正常に機能しているようです。

問題は、ソースコードからサービスを実行しようとしたときです(python service.py installの後にpython service.py startを使用)。これはPythonService.exeを使用してservice.pyをホストします-しかし、私はvenv仮想環境を使用しており、スクリプトはそのモジュールを見つけることができません(エラーメッセージはpython service.py debugで発見されました)。

Pywin32はvirtualenvにインストールされ、PythonService.exeのソースコードを見ると、Python34.dllに動的にリンクし、service.pyをインポートして呼び出します。

Service.pyの実行時にPythonService.exeでvirtualenvを使用するにはどうすればよいですか?

10
David K. Hess

仮想環境がPython 3.3に追加される前は、これはvirtualenvモジュールで正しく機能していたようです。事例証拠があります: https:// stackoverflow.com/a/12424980/1055722 )Pythonのsite.pyは、インポートを満たすディレクトリが見つかるまで実行可能ファイルから上向きに検索していました。その後、それをsys.prefixとこれは、PythonService.exeが内部にあるvirtualenvを見つけて使用するのに十分でした。

それが動作だった場合、site.pyvenvモジュールの導入によりもはやそれを行わないようです。代わりに、pyvenv.cfgファイルを1レベル上で検索し、その場合にのみ仮想環境用に構成します。もちろん、これは、site-packagesの下のpywin32モジュールに埋め込まれているPythonService.exeでは機能しません。

それを回避するために、元のvirtualenvモジュールに付属するactivate_this.pyコードを適応させました(この回答を参照してください: https://stackoverflow.com/a/33637378/1055722 =)。これは、実行可能ファイル(PythonService.exeの場合)に埋め込まれたインタープリターがvirtualenvを使用するためにbootstrapインタープリター)に使用されます。残念ながら、venvにはこれが含まれていません。

これが私のために働いたものです。これは、仮想環境の名前がmy-venvであり、ソースコードの場所の1レベル上にあることを前提としていることに注意してください。

import os
import sys

if sys.executable.endswith("PythonService.exe"):

    # Change current working directory from PythonService.exe location to something better.
    service_directory = os.path.dirname(__file__)
    source_directory = os.path.abspath(os.path.join(service_directory, ".."))
    os.chdir(source_directory)
    sys.path.append(".")

    # Adapted from virtualenv's activate_this.py
    # Manually activate a virtual environment inside an already initialized interpreter.
    old_os_path = os.environ['PATH']
    venv_base = os.path.abspath(os.path.join(source_directory, "..", "my-venv"))
    os.environ['PATH'] = os.path.join(venv_base, "Scripts") + os.pathsep + old_os_path
    site_packages = os.path.join(venv_base, 'Lib', 'site-packages')
    prev_sys_path = list(sys.path)
    import site
    site.addsitedir(site_packages)
    sys.real_prefix = sys.prefix
    sys.prefix = venv_base

    new_sys_path = []
    for item in list(sys.path):
        if item not in prev_sys_path:
            new_sys_path.append(item)
            sys.path.remove(item)
    sys.path[:0] = new_sys_path

私の悩みのもう1つの要因は、pywin32用の新しいpypiホイールがあります。これは、pipを使用して簡単にインストールできるTwistedの人々によって提供されています。そのパッケージのPythonService.exeは、easy_installを使用して公式のwin32 exeパッケージを仮想環境にインストールしたときに得られるものと比較して、奇妙な動作をしていました(呼び出されたときにpywin32 dllを見つけることができませんでした)。

4
David K. Hess

私はすべての答えを読みましたが、解決策で問題を解決することはできません。

慎重に調査した後 David K. Hess のコードを変更したところ、ようやく機能しました。

しかし、私の評判は十分ではないので、ここにコードを投稿するだけです。

# 1. Custom your Project's name and Virtual Environment folder's name
# 2. Import this before all third part models
# 3. If you still failed, check the link below:
# https://stackoverflow.com/questions/34696815/using-pythonservice-exe-to-Host-python-service-while-using-virtualenv
# 2019-05-29 by oraant, modified from David K. Hess's answer.

import os, sys, site

project_name = "PythonService"  # Change this for your own project !!!!!!!!!!!!!!
venv_folder_name = "venv"  # Change this for your own venv path !!!!!!!!!!!!!!

if sys.executable.lower().endswith("pythonservice.exe"):

    # Get root path for the project
    service_directory = os.path.abspath(os.path.dirname(__file__))
    project_directory = service_directory[:service_directory.find(project_name)+len(project_name)]

    # Get venv path for the project
    def file_path(x): return os.path.join(project_directory, x)
    venv_base = file_path(venv_folder_name)
    venv_scripts = os.path.join(venv_base, "Scripts")
    venv_packages = os.path.join(venv_base, 'Lib', 'site-packages')

    # Change current working directory from PythonService.exe location to something better.
    os.chdir(project_directory)
    sys.path.append(".")
    prev_sys_path = list(sys.path)

    # Manually activate a virtual environment inside an already initialized interpreter.
    os.environ['PATH'] = venv_scripts + os.pathsep + os.environ['PATH']

    site.addsitedir(venv_packages)
    sys.real_prefix = sys.prefix
    sys.prefix = venv_base

    # Move some sys path in front of others
    new_sys_path = []
    for item in list(sys.path):
        if item not in prev_sys_path:
            new_sys_path.append(item)
            sys.path.remove(item)
    sys.path[:0] = new_sys_path

それを使用する方法?簡単です。新しいpythonファイルに貼り付けて、次のようなサードパーティモデルの前にインポートするだけです。

import service_in_venv  # import at top
import win32serviceutil
import win32service
import win32event
import servicemanager
import time
import sys, os
........

そして今、あなたはあなたの問題を修正する必要があります。

2
oraant

2018年に読んだ人にとって、上記のどちらのソリューションでも運がなかった(Win10、Python 3.6)-これが私がそれを機能させるためにしたことです。作業ディレクトリはサイトにあります-起動時にpackages/win32であるため、プロジェクトコードをインポートする前に、作業ディレクトリを変更してsys.pathを修正する必要があります。この想定されるvenvはプロジェクトディレクトリにあります。そうでない場合は、いくつかのパスをハードコーディングする必要があります。

import sys
import os
if sys.executable.lower().endswith("pythonservice.exe"):
    for i in range(4): # goes up 4 directories to project folder
        os.chdir("..")        
    # insert site-packages 2nd in path (behind project folder)
    sys.path.insert(1, os.path.join("venv",'Lib','site-packages'))

[REST OF IMPORTS]
class TestService(win32serviceutil.ServiceFramework):
    [...]
2
blakebjorn