Использование PythonService.exe для размещения службы python при использовании virtualenv
У меня есть среда Windows 7, где мне нужно разработать Python Windows Service с использованием Python 3.4. Я использую модуль win32service pywin32 для настройки сервиса, и большинство хуков работают нормально.
Проблема в том, когда я пытаюсь запустить службу из исходного кода (используя 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 и вызывает его.
Как я могу получить PythonService.exe для использования моего virtualenv при запуске service.py?
7 ответов
Похоже, это используется для правильной работы с virtualenv
модуль до виртуальных сред были добавлены в Python 3.3. Есть неподтвержденная информация (см. Этот ответ: /questions/39223258/kak-rabotaet-virtualenv/39223278#39223278), что Python site.py
используется для поиска вверх от исполняемого файла, пока не найдет каталог, который будет удовлетворять импортам. Затем он использовал бы это для sys.prefix
и этого было достаточно для PythonService.exe, чтобы найти virtualenv и использовать его.
Если бы это было поведение, кажется, что site.py
больше не делает это с введением venv
модуль. Вместо этого он выглядит на один уровень выше pyvenv.cfg
файл и настраивает для виртуальной среды только в этом случае. Это, конечно, не работает для PythonService.exe, который скрыт в модуле pywin32 в пакетах сайта.
Чтобы обойти это, я адаптировал activate_this.py
код, который поставляется с оригиналом virtualenv
модуль (см. этот ответ: /questions/32672440/kak-ya-mogu-aktivirovat-pyvenv-vitrualenv-iz-pitona-activthispy-byil-udalen/32672456#32672456). Он используется для начальной загрузки интерпретатора, встроенного в исполняемый файл (как в случае с PythonService.exe), в использование virtualenv. К несчастью, venv
не включает это.
Вот что сработало для меня. Обратите внимание, что предполагается, что виртуальная среда называется my-venv и расположена на один уровень выше расположения исходного кода.
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
Еще один фактор в моих проблемах - это новое колесо pypi для pywin32, которое предоставляют Twisted люди, что облегчает установку с помощью pip. PythonService.exe в этом пакете работал странно (не смог найти DLL-файл pywin32 при вызове) по сравнению с тем, который вы получаете при установке официального пакета win32 exe в виртуальную среду, используя easy_install.
Большое спасибо за размещение этого вопроса и решения. Я выбрал немного другой подход, который также может быть полезен. Довольно сложно найти рабочие советы для служб Python, не говоря уже о том, чтобы делать это с помощью virtualenv. Тем не мение...
меры
Для этого используется Windows 7 x64, Python 3.5.1 x64, pywin32-220 (или pypiwin32-219).
- Откройте командную строку администратора.
- Создать виртуал.
C:\Python35\python -m venv myvenv
- Активируйте virtualenv.
call myvenv\scripts\activate.bat
- Установите pywin32, либо:
- Из Pypi:
pip install pypiwin32
, - С http://www.lfd.uci.edu/~gohlke/pythonlibs/:
pip install path\to\pywin32.whl
- Из Pypi:
- Запустите скрипт после установки
python myvenv\Scripts\pywin32_postinstall.py -install
,- Этот скрипт регистрирует библиотеки DLL в системе и копирует их в
C:\Windows\System32
, Библиотеки названыpythoncom35.dll
а такжеpywintypes35.dll
, Таким образом, виртуальные среды на одной и той же машине с одним и тем же основным выпуском Python разделяют их... это небольшой компромисс:)
- Этот скрипт регистрирует библиотеки DLL в системе и копирует их в
- копия
myvenv\Lib\site-packages\win32\pythonservice.exe
вmyvenv\Scripts\pythonservice.exe
- В классе обслуживания (какими бы ни были подклассы win32serviceutil.ServiceFramework), установите свойство класса
_exe_path_
чтобы указать на этот перемещенный exe. Это станет службой binPath. Например:_exe_path_ = os.path.join(*[os.environ['VIRTUAL_ENV'], 'Scripts', 'pythonservice.exe'])
,
- В классе обслуживания (какими бы ни были подклассы win32serviceutil.ServiceFramework), установите свойство класса
обсуждение
Я думаю, почему это работает, потому что Python смотрит вверх, чтобы выяснить, где находятся папки Libs, и на основе этого устанавливает пути импорта пакетов, аналогично принятому ответу. Когда pythonservice.exe находится в исходном расположении, это, кажется, не работает гладко.
Это также решает проблемы с связыванием DLL (обнаруживается с помощью зависящего от.exe файла http://www.dependencywalker.com/). Без разбора бизнеса DLL невозможно будет импортировать файлы *.pyd из venv\Lib\site-packages\win32
как модули в ваших скриптах. Например, необходимо разрешить import servicemanager
; как servicemanager.pyd
отсутствует в пакете в виде файла.py и обладает некоторыми замечательными возможностями журнала событий Windows.
Одна из проблем, возникших у меня с принятым ответом, заключается в том, что я не мог понять, как заставить его точно подобрать пути package.egg-link, которые создаются при использовании setup.py develop
, Эти файлы.egg-link включают путь к пакету, если он не находится в virtualenv под myvenv\Lib\site-packages
,
Если все прошло гладко, должна быть возможность установить, запустить и протестировать пример службы win32 (из командной строки Admin в активированном virtualenv):
python venv\Lib\site-packages\win32\Demos\service\pipeTestService.py install
python venv\Lib\site-packages\win32\Demos\service\pipeTestService.py start
python venv\Lib\site-packages\win32\Demos\service\pipeTestServiceClient.py
Сервисная среда
Еще одно важное замечание во всем этом заключается в том, что служба будет выполнять код Python в совершенно отдельной среде, чем та, которую вы можете запустить. python myservice.py debug
, Так например os.environ['VIRTUAL_ENV']
будет пустым при запуске службы. Это может быть обработано:
- Установка переменных среды внутри скрипта, например
- Найти текущий путь, начиная с sys.executable, как описано в принятом ответе.
- Используйте этот путь, чтобы найти файл конфигурации.
- Прочитайте файл конфигурации и поместите их в среду с
os.environ
,
- Добавьте ключи реестра в службу с переменными среды.
- См. Доступ к переменным среды из служб Windows для выполнения этого вручную с помощью regedit.exe
- Смотрите REG ADD a REG_MULTI_SZ Многострочное значение реестра для выполнения этого из командной строки.
Я прочитал все ответы, но никакое решение не может решить мою проблему.
После тщательного изучения кода Дэвида К. Хесса я внес некоторые изменения, и в конце концов он работает.
Но моей репутации недостаточно, поэтому я просто публикую код здесь.
# 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://stackru.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
........
И теперь вы должны решить вашу проблему.
Не использовать «pythonservice.exe», зарегистрироваться
python.exe
на услуги напрямую:
import win32serviceutil
import win32service
import servicemanager
import sys
import os
import os.path
import multiprocessing
#
def main():
import time
time.sleep(600)
class ProcessService(win32serviceutil.ServiceFramework):
_svc_name_ = "SleepService"
_svc_display_name_ = "Sleep Service"
_svc_description_ = "Sleeps for 600"
_exe_name_ = sys.executable # python.exe from venv
_exe_args_ = '-u -E "' + os.path.abspath(__file__) + '"'
proc = None
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
if self.proc:
self.proc.terminate()
def SvcRun(self):
self.proc = multiprocessing.Process(target=main)
self.proc.start()
self.ReportServiceStatus(win32service.SERVICE_RUNNING)
self.SvcDoRun()
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
def SvcDoRun(self):
self.proc.join()
def start():
if len(sys.argv)==1:
import win32traceutil
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(ProcessService)
servicemanager.StartServiceCtrlDispatcher()
elif '--fg' in sys.argv:
main()
else:
win32serviceutil.HandleCommandLine(ProcessService)
if __name__ == '__main__':
try:
start()
except (SystemExit, KeyboardInterrupt):
raise
except:
import traceback
traceback.print_exc()
Это делает поддержку python 3.5+ virtualenv для работы, указывая правильный интерпретатор с установкой службы.
Для тех, кто читает в 2018 году, мне не повезло ни с одним из перечисленных выше решений (Win10, Python 3.6) - так что я сделал это, чтобы оно заработало. При запуске рабочий каталог находится в site-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):
[...]
Недавно я столкнулся с этой проблемой во встраиваемом интерпретаторе и придумал эти инструкции, которые работают для Python 3.11 как во встраиваемом интерпретаторе, так и в обычной виртуальной среде.
Это решение отличается от самого популярного на данный момент ответа тем, что оно не использует какие-либо общие библиотеки DLL.C:\windows\system32
.
Наконец, предоставляется скрипт Python, демонстрирующий необходимую настройку среды.
Виртуальная среда
PS D:\dev\python_winsvc> C:\Python\Python311\python.exe -m venv venv_311
PS D:\dev\python_winsvc> . .\venv_311\Scripts\activate
(venv_311) PS D:\dev\python_winsvc> pip install pywin32
Collecting pywin32
Using cached pywin32-306-cp311-cp311-win_amd64.whl (9.2 MB)
Installing collected packages: pywin32
Successfully installed pywin32-306
(venv_311) PS D:\dev\python_winsvc> deactivate
Сделайте так:
python.exe # already there
python3.dll # copy/link from venv source interpreter
python311.dll # copy/link from venv source interpreter
pythoncom311.dll # copy/link from .\venv_311\Lib\site-packages\pywin32_system32
pywintypes311.dll # copy/link from .\venv_311\Lib\site-packages\pywin32_system32
pythonservice.exe # copy/link from .\venv_311\Lib\site-packages\win32
servicemanager.pyd # copy/link from .\venv_311\Lib\site-packages\win32
...
Выполните установку службы без активации venv . В противном случае в качестве исполняемого файла службы используется неправильный файл pythonservice.exe, и он должен быть тот, который находится в.\venv_311\Scripts
поскольку он должен находиться в той же папке, что и требуемая DLL.
PS D:\dev\python_winsvc> .\venv_311\Scripts\python .\winsvc.py install
Installing service python-winsvc
Service installed
PS D:\dev\python_winsvc> .\venv_311\Scripts\python .\winsvc.py start
Starting service python-winsvc
Встроенный переводчик
Обратите внимание, что мы готовим пакеты pip для встраиваемого интерпретатора, используя регулярно устанавливаемый пакет, поскольку во встраиваемом интерпретаторе отсутствует модуль pip.
PS D:\dev\python_winsvc> C:\Python\Python311\python.exe -m pip install --target embed_311\lib\site-packages pywin32
Collecting pywin32
Using cached pywin32-306-cp311-cp311-win_amd64.whl (9.2 MB)
Installing collected packages: pywin32
Successfully installed pywin32-306
Делать.\embed_311
выглядеть так:
python.exe # already there
python3.dll # already there
python311.dll # already there
pythoncom311.dll # copy/link from .\embed_311\Lib\site-packages\pywin32_system32
pywintypes311.dll # copy/link from .\embed_311\Lib\site-packages\pywin32_system32
pythonservice.exe # copy/link from .\embed_311\Lib\site-packages\win32
servicemanager.pyd # copy/link from .\embed_311\Lib\site-packages\win32
...
PS D:\dev\python_winsvc> .\embed_311\python.exe .\winsvc.py install
Installing service python-winsvc
Service installed
PS D:\dev\python_winsvc> .\embed_311\python.exe .\winsvc.py start
Starting service python-winsvc
Пример услуги
# winsvc.py
import sys
import pathlib
PYTHON_PATH = pathlib.Path(sys.executable).parent
import site
site.addsitedir(PYTHON_PATH.joinpath("lib/site-packages")) # Only required when using the embedded interpreter
from typing import *
import logging, logging.handlers
import threading
import time
import win32event
import win32evtlogutil
import win32service
import win32serviceutil
import servicemanager
def configure_logger(filename: str) -> logging.Logger:
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(levelname)5.5s: %(message)s")
handlers = [
logging.handlers.RotatingFileHandler(pathlib.Path(__file__).parent.joinpath(filename), maxBytes=1024*1024, backupCount=0),
logging.StreamHandler()
]
for handler in handlers:
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
logger = configure_logger("winsvc.log")
class ApplicationThread(threading.Thread):
def __init__(self) -> None:
super().__init__()
self._exit = False
def stop(self) -> None:
self._exit = True
def run(self) -> None:
logger.debug("service is running")
while not self._exit:
time.sleep(1)
class Win32ServiceWrapper(win32serviceutil.ServiceFramework):
_exe_name_ = str(PYTHON_PATH.joinpath("pythonservice.exe"))
_svc_name_ = "python-winsvc"
_svc_display_name_ = "Python WinSvc"
def __init__(self, args: Iterable[str]) -> None:
super().__init__(args)
self._stop_event = win32event.CreateEvent(None, 0, 0, None)
self._thread = ApplicationThread()
def SvcStop(self) -> None:
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self._stop_event)
def SvcDoRun(self):
win32evtlogutil.ReportEvent(self._svc_display_name_, servicemanager.PYS_SERVICE_STARTED, 0, servicemanager.EVENTLOG_INFORMATION_TYPE, (self._svc_name_, ''))
self._thread.start()
win32event.WaitForSingleObject(self._stop_event, win32event.INFINITE)
self._thread.stop()
self._thread.join()
win32evtlogutil.ReportEvent(self._svc_display_name_, servicemanager.PYS_SERVICE_STOPPED, 0, servicemanager.EVENTLOG_INFORMATION_TYPE, (self._svc_name_, ''))
if __name__ == "__main__":
win32serviceutil.HandleCommandLine(Win32ServiceWrapper)
Здесь важно помнить (для тех, кто плохо знаком с сервисами), что при запуске сценария через службы Windows используется PythonService.exe, а при его запуске напрямую используется python.exe.
Команда №1:
call .\env\Scripts\python3.exe server_windows10_app\\main.py
Результат:
sys.executable.lower(): c:\u...\env\scripts\python3.exe
Команда №2:
call .\env\Scripts\python3.exe .\server.py --startup=delayed install
call .\env\Scripts\python3.exe .\server.py start
Результат:
sys.executable.lower(): c:\...\env\lib\site-packages\win32\pythonservice.exe