2016-01-09 28 views
5

Ho un ambiente Windows 7 in cui ho bisogno di sviluppare un servizio Python per Windows utilizzando Python 3.4. Sto usando il modulo win32service di pywin32 per configurare il servizio e la maggior parte dei ganci sembra funzionare bene.Utilizzo di PythonService.exe per l'hosting del servizio python durante l'utilizzo di virtualenv

Il problema si verifica quando si tenta di eseguire il servizio dal codice sorgente (utilizzando python service.py install seguito da python service.py start). Questo utilizza PythonService.exe per ospitare service.py - ma sto usando un ambiente virtuale venv e lo script non riesce a trovare i suoi moduli (messaggio di errore scoperto con python service.py debug).

Pywin32 è installato in virtualenv e, guardando il codice sorgente di PythonService.exe, si collega dinamicamente in Python34.dll, importa il mio service.py e lo richiama.

Come posso ottenere PythonService.exe per utilizzare il mio virtualenv quando si esegue il mio service.py?

+0

questione connessa: http://stackoverflow.com/questions/27462582/how-can-i-activate-a-pyvenv-vitrualenv-from-within-python-activate-this-py-was –

risposta

2

Appare usato per funzionare correttamente con il modulo virtualenv prima che gli ambienti virtuali fossero aggiunti a Python 3.3. Esistono prove aneddotiche (si veda questa risposta: https://stackoverflow.com/a/12424980/1055722) che Python ha utilizzato lo site.py per guardare in alto dal file eseguibile fino a quando non ha trovato una directory in grado di soddisfare le importazioni. Lo userebbe quindi per sys.prefix e questo era sufficiente per PythonService.exe per trovare il virtualenv in cui si trovava e usarlo.

Se questo era il comportamento, sembra che site.py non fa che con l'introduzione del modulo venv. Invece, sembra un livello superiore per un file pyvenv.cfg e si configura per un ambiente virtuale solo in quel caso. Questo ovviamente non funziona per PythonService.exe che è sepolto nel modulo pywin32 sotto site-packages.

Per risolvere il problema, ho adattato il codice activate_this.py fornito con il modulo originale virtualenv (vedere questa risposta: https://stackoverflow.com/a/33637378/1055722). È utilizzato per eseguire il bootstrap di un interprete incorporato in un eseguibile (come nel caso di PythonService.exe) nell'utilizzo di un virtualenv. Sfortunatamente, venv non include questo.

Ecco cosa ha funzionato per me. Nota, questo presuppone che l'ambiente virtuale si chiami my-venv e si trovi un livello sopra la posizione del codice sorgente.

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 

Un altro fattore di miei guai - c'è una nuova ruota per PyPI pywin32 che viene fornito dalla gente torti che rende più facile da installare con pip. PythonService.exe in quel pacchetto si comportava in modo strano (non riusciva a trovare una dll pywin32 quando veniva richiamato) rispetto a quello che si ottiene quando si installa il pacchetto exe win32 ufficiale nell'env virtuale usando easy_install.

+0

Nota, mentre ho accettato questa risposta, l'altra ha fornito una anche molto buona. Li contrasterei dicendo che questa soluzione si concentra su un approccio che può essere inserito nel codice sorgente dell'applicazione e non richiede la modifica del sistema (DLL, impostazioni di registro, posizioni exe, ecc.). –

6

Grazie mille per aver postato questa domanda e una soluzione. Ho adottato un approccio leggermente diverso che potrebbe anche essere utile. È piuttosto difficile trovare suggerimenti utili per i servizi Python, e tanto meno farlo con un virtualenv. Comunque ...

Passi

Questo sta usando Windows 7 x64, Python 3.5.1 x64, pywin32-220 (o pypiwin32-219).

  • Aprire un prompt dei comandi di amministratore.
  • Creare un virtualenv. C:\Python35\python -m venv myvenv
  • Attiva il virtualenv.call myvenv\scripts\activate.bat
  • Installare pywin32, sia:
  • Eseguire lo script post-installazione python myvenv\Scripts\pywin32_postinstall.py -install.
    • Questo script registra le DLL nel sistema e le copia su C:\Windows\System32. Le DLL sono denominate pythoncom35.dll e pywintypes35.dll. Così ambienti virtuali sulla stessa macchina sulla stessa major release punto Python condivideranno questi ... si tratta di un compromesso minore :)
  • Copia myvenv\Lib\site-packages\win32\pythonservice.exe al myvenv\Scripts\pythonservice.exe
    • Sulla classe di servizio (qualunque sottoclassi win32serviceutil. ServiceFramework), impostare la proprietà della classe _exe_path_ in modo che punti a questo exe trasferito. Questo diventerà il servizio binPath. Ad esempio: _exe_path_ = os.path.join(*[os.environ['VIRTUAL_ENV'], 'Scripts', 'pythonservice.exe']).

Discussione

Penso che il motivo per cui questo funziona è che Python guarda verso l'alto per capire dove le cartelle Libs sono e sulla base di tale imposta i percorsi di importazione del pacchetto, in modo simile alla risposta accettata. Quando pythonservice.exe si trova nella posizione originale, non sembra funzionare correttamente.

Risolve anche i problemi di collegamento DLL (rilevabile con depends.exe da http://www.dependencywalker.com/). Senza il business DLL risolto, non sarà possibile importare dai file * .pyd da venv\Lib\site-packages\win32 come moduli nei propri script. Ad esempio è necessario consentire import servicemanager; come servicemanager.pyd non è incluso nel pacchetto come file .py e ha alcune fantastiche funzionalità di registro eventi di Windows.

Uno dei problemi che ho avuto con la risposta accettata è che non sono riuscito a capire come ottenere per raccogliere in modo accurato sui percorsi package.egg-link che vengono creati quando si utilizza setup.py develop. Questi file .egg-link includono il percorso del pacchetto quando non si trova nella virtualenv sotto myvenv\Lib\site-packages.

Se tutto è andato liscio, dovrebbe essere possibile installare, avviare e testare il servizio esempio Win32 (da un prompt Admin nel virtualenv attivato):

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 

Il Servizio Ambiente

altro Nota importante in tutto questo è che il servizio eseguirà il codice Python in un ambiente completamente separato da quello che potresti eseguire python myservice.py debug. Ad esempio, ad esempio os.environ['VIRTUAL_ENV'] sarà vuoto durante l'esecuzione del servizio. Può essere gestito da:

  • Impostare le variabili di ambiente dall'interno dello script, ad es.
    • Trova il percorso corrente a partire dal file sys.executable, come descritto nella risposta accettata.
    • Utilizzare quel percorso per individuare un file di configurazione.
    • Leggere il file di configurazione e inserirli nell'ambiente con os.environ.
  • Aggiungere le chiavi di registro al servizio con le variabili di ambiente.
+0

Ben fatto! È bello avere un'altra soluzione a questo problema. –

+1

L'installazione di 'pypiwin32' e l'esecuzione di' pywin32_postinstall.py' non funziona poichè cerca la directory 'pywin32_system32', ma nel caso di' pypiwin32' è 'pypiwin32_system32' (c'è * pi * nel mezzo). – kishkin

+0

Anche uno non dovrebbe impostare il parametro '_exe_path_', ma' _exe_name_'. E molto probabilmente dovrebbe essere racchiuso in 'if not hasattr (sys, 'frozen'):'. Grazie per le vostre istruzioni dettagliate! – kishkin