55import signal
66import socket
77import time
8+ from multiprocessing import Process
9+ from typing import Any
10+ from typing import cast
11+ from typing import Protocol
12+ from typing import Union
813
914import pytest
1015
1116
17+ class _SupportsFlaskAppRun (Protocol ):
18+ def run (
19+ self ,
20+ host : Union [str , None ] = None ,
21+ port : Union [int , None ] = None ,
22+ debug : Union [bool , None ] = None ,
23+ load_dotenv : bool = True ,
24+ ** options : Any ,
25+ ) -> None :
26+ ...
27+
28+
1229# force 'fork' on macOS
1330if platform .system () == "Darwin" :
14- multiprocessing = multiprocessing .get_context ("fork" )
31+ multiprocessing = multiprocessing .get_context ("fork" ) # type: ignore[assignment]
1532
1633
1734class LiveServer : # pragma: no cover
@@ -25,18 +42,25 @@ class LiveServer: # pragma: no cover
2542 application is not started.
2643 """
2744
28- def __init__ (self , app , host , port , wait , clean_stop = False ):
45+ def __init__ (
46+ self ,
47+ app : _SupportsFlaskAppRun ,
48+ host : str ,
49+ port : int ,
50+ wait : int ,
51+ clean_stop : bool = False ,
52+ ):
2953 self .app = app
3054 self .port = port
3155 self .host = host
3256 self .wait = wait
3357 self .clean_stop = clean_stop
34- self ._process = None
58+ self ._process : Union [ Process , None ] = None
3559
36- def start (self ):
60+ def start (self ) -> None :
3761 """Start application in a separate process."""
3862
39- def worker (app , host , port ) :
63+ def worker (app : _SupportsFlaskAppRun , host : str , port : int ) -> None :
4064 app .run (host = host , port = port , use_reloader = False , threaded = True )
4165
4266 self ._process = multiprocessing .Process (
@@ -45,7 +69,7 @@ def worker(app, host, port):
4569 self ._process .daemon = True
4670 self ._process .start ()
4771
48- keep_trying = True
72+ keep_trying : bool = True
4973 start_time = time .time ()
5074 while keep_trying :
5175 elapsed_time = time .time () - start_time
@@ -57,7 +81,7 @@ def worker(app, host, port):
5781 if self ._is_ready ():
5882 keep_trying = False
5983
60- def _is_ready (self ):
84+ def _is_ready (self ) -> bool :
6185 sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
6286 try :
6387 sock .connect ((self .host , self .port ))
@@ -69,13 +93,13 @@ def _is_ready(self):
6993 sock .close ()
7094 return ret
7195
72- def url (self , url = "" ):
96+ def url (self , url : str = "" ) -> str :
7397 """Returns the complete url based on server options."""
7498 return "http://{host!s}:{port!s}{url!s}" .format (
7599 host = self .host , port = self .port , url = url
76100 )
77101
78- def stop (self ):
102+ def stop (self ) -> None :
79103 """Stop application process."""
80104 if self ._process :
81105 if self .clean_stop and self ._stop_cleanly ():
@@ -84,14 +108,17 @@ def stop(self):
84108 # If it's still alive, kill it
85109 self ._process .terminate ()
86110
87- def _stop_cleanly (self , timeout = 5 ) :
111+ def _stop_cleanly (self , timeout : int = 5 ) -> bool :
88112 """Attempts to stop the server cleanly by sending a SIGINT
89113 signal and waiting for ``timeout`` seconds.
90114
91115 :return: True if the server was cleanly stopped, False otherwise.
92116 """
117+ if not self ._process :
118+ return True
119+
93120 try :
94- os .kill (self ._process .pid , signal .SIGINT )
121+ os .kill (cast ( int , self ._process .pid ) , signal .SIGINT )
95122 self ._process .join (timeout )
96123 return True
97124 except Exception as ex :
0 commit comments