Python Flask as Windows Service
Asked Answered
N

3

17

I am trying to get a Flask app to run as a Service in Windows. I have already tried to implement a solution as suggested here and here without success.

I have a simple folder with just two files:

Project
 |
 +-- myapp.py   
 +-- win32_service.py

Inside myapp.py is a simple Flask app:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

And the service skeleton win32_service.py:

import win32serviceutil
import win32service
import win32event
import win32evtlogutil
import servicemanager
import socket
import time
import logging
import os
import sys

sys.path.append(os.path.dirname(__name__))

from myapp import app

logging.basicConfig(
    filename = r'c:\tmp\flask-service.log',
    level = logging.DEBUG, 
    format = '[flaskapp] %(levelname)-7.7s %(message)s'
)

class HelloFlaskSvc (win32serviceutil.ServiceFramework):
    _svc_name_ = "FlaskApp"
    _svc_display_name_ = "FlaskApp Service"

    def __init__(self, *args):
        win32serviceutil.ServiceFramework.__init__(self, *args)
        self.hWaitStop = win32event.CreateEvent(None,0,0,None)
        socket.setdefaulttimeout(5)
        self.stop_requested = False

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)
        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
        logging.info('Stopped service ...')
        self.stop_requested = True

    def SvcDoRun(self):
        servicemanager.LogMsg(
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            servicemanager.PYS_SERVICE_STARTED,
            (self._svc_name_,'')
        )

        self.main()

    def main(self):
        app.run(host="127.0.0.1", port=8000)

if __name__ == '__main__':
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(HelloFlaskSvc)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(HelloFlaskSvc)

I then compiled this to an exe file via pyinstaller using this command:

pyinstaller --onefile --hidden-import win32timezone win32_service.py

I get the compiled exe successfully built. I then proceed to register the service (open cmd with admin privileges):

>>> win32_service.exe install
> Installing service FlaskApp
> Service installed

And I try to start it:

>>> win32_service.exe start
> Starting service FlaskApp

But then nothing happens (no errors). Also if I try to start it from the Task Manager it changes the Status to Starting and then to Stopped.

These are the modules installed in the virtualenv:

altgraph==0.16.1
Click==7.0
Flask==1.0.2
future==0.17.1
itsdangerous==1.1.0
Jinja2==2.10.1
macholib==1.11
MarkupSafe==1.1.1
pefile==2018.8.8
PyInstaller==3.4
pyodbc==4.0.26
pywin32==224
pywin32-ctypes==0.2.0
Werkzeug==0.15.2

System specs:

Python - 3.6.5 
OS     - Windows 10

What I am missing here? Any help is appreciated.

EDIT

Windows EventViewer shows an error:

The description for Event ID 3 from source FlaskApp cannot be found. Either the component that raises this event is not installed on your local computer or the installation is corrupted. You can install or repair the component on the local computer.

If the event originated on another computer, the display information had to be saved with the event.

The following information was included with the event: 

Traceback (most recent call last):
  File "lib\site-packages\win32\lib\win32serviceutil.py", line 839, in SvcRun
  File "win32_service.py", line 47, in SvcDoRun
  File "win32_service.py", line 50, in main
  File "lib\site-packages\flask\app.py", line 938, in run
  File "lib\site-packages\flask\cli.py", line 629, in show_server_banner
  File "lib\site-packages\click\utils.py", line 260, in echo
SystemError: <built-in method replace of str object at 0x000001E36AD465D0> returned a result with an error set

EDIT 2

If I use a single spec file, some modules are not found by the hidden import (this is the output from pyinstaller:

4972 INFO: Analyzing hidden import 'ClickFlask'
4973 ERROR: Hidden import 'ClickFlask' not found
4974 INFO: Analyzing hidden import 'future'
4981 INFO: Analyzing hidden import 'itsdangerous'
5029 INFO: Analyzing hidden import 'Jinja2'
5030 ERROR: Hidden import 'Jinja2' not found
5030 INFO: Analyzing hidden import 'MarkupSafe'
5032 ERROR: Hidden import 'MarkupSafe' not found
5033 INFO: Analyzing hidden import 'pyodbc'
5034 INFO: Analyzing hidden import 'pywin32'
5035 ERROR: Hidden import 'pywin32' not found
5035 INFO: Analyzing hidden import 'pywin32-ctypes'
5036 ERROR: Hidden import 'pywin32-ctypes' not found

Could it have to do with this? Why are some modules found and others don't? I am using a virtualenv.

Nineteen answered 14/4, 2019 at 15:48 Comment(16)
Does it log an event in the EventViewer? Do you see any helpful message there?Dukas
@Dukas Thanks for pointing that. Yes, will update with the error from the EventViewer.Nineteen
How about you combine the code from the two files into one Python script and try to see if it works. Later they can be separated into individual scripts. This SO answer may help.Dukas
No luck, I get that same error if I only use a single Python script..Nineteen
Ok, I'll test it on my Windows box and share my resultsDukas
Take a look at this: github.com/top2topii/FlaskServiceWin32Sverige
Did you try it without UPX?Treulich
Ok, Try pip freeze within your virtualenv, unfortunately I don't know much about Windows, but using something like GIT BASH u can use command like which flask and stuff, this way u can check if it's running the right env or it's using global Python installationTreulich
@Treulich I already added the pip freeze result in my post above, the modules are correctly installed in the virtualenv, but somehow the pyinstaller analyzer can't find some of them...Nineteen
@Nineteen then use which pyinstaller command and look it upTreulich
All the scripts (python.exe, flask.exe, pyinstaller.exe) are starting correctly from the virtualenv folder...Nineteen
What's your Python version?Treulich
Are u using Windows 10? or What?Treulich
PyInstaller: 3.4, Python: 3.6.5, Windows-10Nineteen
@Nineteen Probably the problem is Windows 10, Can u use a Virtual Machine with Windows 7 to freeze the service executable? Similar problems: StackOverflow QuestionTreulich
Let us continue this discussion in chat.Treulich
N
7

I looked further into pyinstaller github repo and solved this issue.

It seems that pyinstaller has some conflicts with Windows 10, but this issue was the key to my problem. Althoug the module producing the error was not the same.

I managed to solve it by adding a SystemError exception at lib\site-packages\click\utils.py, line 260 in the echo function.

So I change this:

if message:
   file.write(message)

To this:

if message:
    try:
        file.write(message)
    except SystemError:
        pass

Rebuilt the exe using:

pyinstaller --onefile --hidden-import win32timezone win32_service.py

Installed the service, and then it started correctly.

Nineteen answered 17/4, 2019 at 10:53 Comment(0)
T
7

According to a Reddit post, Adding all the libraries to hiddenimports should fix your problem, I tried it myself and it did work!

So, create a file in your project's directory, named win32_service.spec with the following content

# -*- mode: python -*-

block_cipher = None


a = Analysis(['win32_service.py'],
             pathex=['C:\\Users\\Win7\\Desktop\\FaaS'],
             binaries=[],
             datas=[],
             hiddenimports=['win32timezone',
                            'altgraph',
                            'Click'
                            'Flask',
                            'future',
                            'itsdangerous',
                            'Jinja2',
                            'macholib',
                            'MarkupSafe',
                            'pefile',
                            'PyInstaller',
                            'pyodbc',
                            'pywin32',
                            'pywin32-ctypes',
                            'Werkzeug',],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='win32_service',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          runtime_tmpdir=None,
          console=True )

Don't forget to change pathex variable

Then instead of pyinstaller --onefile --hidden-import win32timezone win32_service.py use the following command: pyinstaller --onefile win32_service.spec

Treulich answered 17/4, 2019 at 6:47 Comment(4)
Sorry, but I get the same error File "lib\site-packages\click\utils.py", line 260, in echo, and the service doesn't start..Nineteen
@Nineteen Did you try win32_service.exe debug?Treulich
It gives a Fatal error detected: Failed to execute script win32_serviceNineteen
@Nineteen I just saw the new update, it's the modulesTreulich
N
7

I looked further into pyinstaller github repo and solved this issue.

It seems that pyinstaller has some conflicts with Windows 10, but this issue was the key to my problem. Althoug the module producing the error was not the same.

I managed to solve it by adding a SystemError exception at lib\site-packages\click\utils.py, line 260 in the echo function.

So I change this:

if message:
   file.write(message)

To this:

if message:
    try:
        file.write(message)
    except SystemError:
        pass

Rebuilt the exe using:

pyinstaller --onefile --hidden-import win32timezone win32_service.py

Installed the service, and then it started correctly.

Nineteen answered 17/4, 2019 at 10:53 Comment(0)
G
1

This problem is due to pywin32, install pywin32 executable, avoid installing it using pip, also make sure to use the --hidden-import=win32timezone flag while compiling it using pyinstaller.

Also run the service from CMD (Administrator)

Here is the step by step Beginner Guide : https://github.com/PushpenderIndia/PythonWindowsService

Also Posting the same solution here as well.

Steps To Create an Python Windows Service

(1) Copy Paste These Codes to a Python File (e.g. server.py)

import servicemanager
import sys
import win32serviceutil
from mainserver import FlaskServer   # Import your code, I've written a module called mainserver which contains FlaskServer Code using OOPs
import threading
import concurrent.futures
import time

class workingthread(threading.Thread):
    def __init__(self, quitEvent):
        self.quitEvent = quitEvent
        self.waitTime = 1
        threading.Thread.__init__(self)

    def run(self):
        try:
            # Running start_flask() function on different thread, so that it doesn't blocks the code
            executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
            executor.submit(self.start_flask)
        except:
            pass

        # Following Lines are written so that, the program doesn't get quit
        # Will Run a Endless While Loop till Stop signal is not received from Windows Service API
        while not self.quitEvent.isSet():  # If stop signal is triggered, exit
            time.sleep(1)

    def start_flask(self):
        # This Function contains the actual logic, of windows service
        # This is case, we are running our flaskserver
        test = FlaskServer()
        test.start()

class FlaskService(win32serviceutil.ServiceFramework):
    _svc_name_ = "AA Testing"
    _svc_display_name_ = "AAA Testing"
    _svc_description_ = "This is my service"

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = threading.Event()
        self.thread = workingthread(self.hWaitStop)

    def SvcStop(self):
        self.hWaitStop.set()

    def SvcDoRun(self):
        self.thread.start()
        self.hWaitStop.wait()
        self.thread.join()


if __name__ == '__main__':
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(FlaskService)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(FlaskService)

(2) Install the latest pywin32.exe

  • NOTE: If you install pywin32 using pip, then it will not going to install properly, thus will show you some errors,
  • Visit https://github.com/mhammond/pywin32/releases
  • And Download the latest exe, If you are using Python 32bit then download pywin32-302.win32-py3.x.exe
  • If using Python 64 bit, then download pywin32-302.win-amd64-py3.x.exe

(3) Compile your server.py using pyinstaller

  • Compiling service executable
C:\Users\Pushpender\Desktop> python -m pip install servicemanager
C:\Users\Pushpender\Desktop> pyinstaller --onefile server.py --hidden-import=win32timezone --clean --uac-admin
  • Installing & Running service executable (Run CMD as Administrator)
C:\WINDOWS\system32>cd C:\Users\Pushpender\Desktop>
C:\WINDOWS\system32>d:
C:\Users\Pushpender\Desktop>server.exe --startup=auto install       # Installing service with startup == Automatic    
C:\Users\Pushpender\Desktop>server.exe start    # For starting service (You can start from Windows Service or From Task Manager)
C:\Users\Pushpender\Desktop>server.exe stop     # For stopping service (You can stop from Windows Service or From Task Manager)
C:\Users\Pushpender\Desktop>server.exe remove   # For removing installed service

(4) You can run server.py directly without compiling (Run CMD as Administrator)

C:\WINDOWS\system32>cd C:\Users\Pushpender\Desktop>
C:\WINDOWS\system32>d:
C:\Users\Pushpender\Desktop>python server.py --startup=auto install   # Installing service with startup == Automatic   
C:\Users\Pushpender\Desktop>python server.py start     # For starting service (You can start from Windows Service or From Task Manager)
C:\Users\Pushpender\Desktop>python server.py stop      # For stopping service (You can stop from Windows Service or From Task Manager)
C:\Users\Pushpender\Desktop>python server.py remove    # For removing installed service

NOTE:

  • You can tweak the above code, for example, you can change the following things

      _svc_display_name_ = "AAA Testing"
      _svc_description_ = "This is my service"
    
  • Also you can change the classes names of FlaskService , workingthread

  • Please change this line in your code from mainserver import FlaskServer

  • You can change the startup type to lots of things, type server.exe --help in order to know more about settings

  • If you want to set the StartUp= Manaull, then don't use --startup=auto, while installing service

  • If you want to set the StartUp= Automatic (Delayed), then use --startup=delayed, while installing service

  • Use --startup argument before install argument

  • If you have used any kind of paths in your code such as "/logging/logs.txt", then Make sure to use full/absolute paths in your code, such as "C:/logging/logs.txt". Because windows will going to call your service from any other path

  • Use this command in order to run your service in debug mode: server.exe debug

(4) Integrate Windows server with Inno Setup Builder

  • If you want to create a Installer which will Install your service at Installion process & will remove it at Uninstall, then
  • Add these block of code in your script.iss
[Run]
Filename: "{app}\{#MyAppExeName}"; StatusMsg: "Installing Windows Service ... "; Parameters: "--startup=delayed install";  Flags: runhidden waituntilterminated  
Filename: "{app}\{#MyAppExeName}"; StatusMsg: "Running Windows Service ... "; Parameters: "start";  Flags: runhidden waituntilterminated

[UninstallRun]
Filename: "{app}\{#MyAppExeName}"; Parameters: "remove";  Flags: runhidden
Grosso answered 28/10, 2021 at 8:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.