kopia lustrzana https://github.com/cirospaciari/socketify.py
cleaning up
rodzic
53a6456624
commit
66b00f8fa7
|
@ -3,7 +3,9 @@ import os
|
||||||
import logging
|
import logging
|
||||||
import glob
|
import glob
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
from . import App, AppOptions, AppListenOptions
|
from . import App, AppOptions, AppListenOptions
|
||||||
|
|
||||||
help = """
|
help = """
|
||||||
|
@ -43,7 +45,7 @@ Options:
|
||||||
--task-factory-maxitems INT Pre allocated instances of Task objects for socketify, ASGI interface [default: 100000]
|
--task-factory-maxitems INT Pre allocated instances of Task objects for socketify, ASGI interface [default: 100000]
|
||||||
|
|
||||||
--reload Enable auto-reload. This options also disable --workers or -w option.
|
--reload Enable auto-reload. This options also disable --workers or -w option.
|
||||||
--reload-ignore-patterns Comma delimited list of ignore strings Default "__pycache__,node_modules,build,target,.git" could include gitignore?
|
--reload-ignore-patterns Comma delimited list of ignore strings Default "__pycache__,node_modules,.git"
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
python3 -m socketify main:app -w 8 -p 8181
|
python3 -m socketify main:app -w 8 -p 8181
|
||||||
|
@ -95,9 +97,10 @@ def str_bool(text):
|
||||||
return text == "true"
|
return text == "true"
|
||||||
|
|
||||||
class ReloadState:
|
class ReloadState:
|
||||||
# Class object to store reload state
|
""" Object to store reload state
|
||||||
# Windows only catches (SIGTERM) but it's also used
|
Windows only catches (SIGTERM) but it's also used
|
||||||
# for other purposes, so we set a switch
|
for other purposes, so we set a switch so that execuet() knows whether
|
||||||
|
to restart or shut down """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reload_pending = False
|
self.reload_pending = False
|
||||||
|
|
||||||
|
@ -123,8 +126,6 @@ def load_module(file, reload=False):
|
||||||
|
|
||||||
|
|
||||||
def execute(args):
|
def execute(args):
|
||||||
print('cli.execute')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_execute(args)
|
_execute(args)
|
||||||
except SystemExit as se:
|
except SystemExit as se:
|
||||||
|
@ -132,13 +133,6 @@ def execute(args):
|
||||||
if 'reload' in str(se) and '--reload' in args and reload_state.reload_pending:
|
if 'reload' in str(se) and '--reload' in args and reload_state.reload_pending:
|
||||||
logging.info('RELOADING...')
|
logging.info('RELOADING...')
|
||||||
reload_state.reload_pending = False
|
reload_state.reload_pending = False
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
#print(args)
|
|
||||||
#print(sys.argv)
|
|
||||||
|
|
||||||
#os.execv(sys.executable, ['-m socketify'] + args[1:])
|
|
||||||
#print(sys.executable, [sys.executable, '-m', 'socketify'] + args[1:])
|
|
||||||
|
|
||||||
# The app.run has already caught SIGTERM which closes the loop then raises SystemExit.
|
# The app.run has already caught SIGTERM which closes the loop then raises SystemExit.
|
||||||
# SIGTERM works across both Windows and Linux
|
# SIGTERM works across both Windows and Linux
|
||||||
|
@ -150,8 +144,7 @@ def execute(args):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
# *ix
|
# *ix
|
||||||
os.execv(sys.executable, [sys.executable, '-m', 'socketify'] + args[1:])
|
os.execv(sys.executable, [sys.executable, '-m', 'socketify'] + args[1:])
|
||||||
#os.kill(os.getpid(), signal.SIGINT) <-- this done in the file probe
|
|
||||||
#or os.popen("wmic process where processid='{}' call terminate".format(os.getpid()))
|
|
||||||
|
|
||||||
def _execute(args):
|
def _execute(args):
|
||||||
arguments_length = len(args)
|
arguments_length = len(args)
|
||||||
|
@ -178,32 +171,22 @@ def _execute(args):
|
||||||
options_list = args[2:]
|
options_list = args[2:]
|
||||||
options = {}
|
options = {}
|
||||||
selected_option = None
|
selected_option = None
|
||||||
# lets try argparse in parallel
|
|
||||||
import argparse
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('--reload', default=False, action='store_true', help='reload the server on file changes, see --reload-ignore-patterns')
|
|
||||||
parser.add_argument('rem_args', nargs=argparse.REMAINDER) # Can move the other options here too
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
for option in options_list:
|
for option in options_list:
|
||||||
if selected_option:
|
if selected_option:
|
||||||
options[selected_option] = option
|
options[selected_option] = option
|
||||||
selected_option = None
|
selected_option = None
|
||||||
elif option.startswith("--") or option.startswith("-"):
|
elif option.startswith("--") or option.startswith("-"):
|
||||||
if selected_option is None: # ??
|
if selected_option is None:
|
||||||
selected_option = option # ??
|
selected_option = option # here, say i want to pass an arg to my app if you do --dev --reload you get "--dev": "--reload"')
|
||||||
else: # --factory, --reload etc
|
else: # --factory, --reload etc
|
||||||
options[selected_option] = True
|
options[selected_option] = True
|
||||||
else:
|
else:
|
||||||
return print(f"Invalid option ${selected_option} see --help")
|
return print(f"Invalid option ${selected_option} see --help")
|
||||||
if selected_option: # --factory, --reload etc
|
if selected_option: # --factory, --reload etc
|
||||||
options[selected_option] = True
|
options[selected_option] = True
|
||||||
print(options)
|
|
||||||
print('OPTIONS', flush=True)
|
|
||||||
print('BUG here, say i want to pass an arg to my app if you do --dev --reload you get "--dev": "--reload"')
|
|
||||||
print(options.get('--reload'))
|
|
||||||
print(args.reload)
|
|
||||||
print(args.rem_args)
|
|
||||||
interface = (options.get("--interface", "auto")).lower()
|
interface = (options.get("--interface", "auto")).lower()
|
||||||
|
|
||||||
if interface == "auto":
|
if interface == "auto":
|
||||||
|
@ -348,32 +331,24 @@ def _execute(args):
|
||||||
)
|
)
|
||||||
|
|
||||||
# file watcher
|
# file watcher
|
||||||
def launch_with_file_probe(run_method, user_module_function, loop, poll_frequency=20):
|
def launch_with_file_probe(run_method, user_module_function, loop, poll_frequency=4):
|
||||||
import asyncio
|
|
||||||
import importlib.util
|
import importlib.util
|
||||||
directory = os.path.dirname(importlib.util.find_spec(user_module_function.__module__).origin)
|
directory = os.path.dirname(importlib.util.find_spec(user_module_function.__module__).origin)
|
||||||
directory_glob = os.path.join(directory, '**')
|
directory_glob = os.path.join(directory, '**')
|
||||||
logging.info("Watching %s" % directory_glob)
|
|
||||||
print("Watching %s" % directory_glob, flush=True)
|
print("Watching %s" % directory_glob, flush=True)
|
||||||
ignore_patterns = options.get("--reload-ignore-patterns", "node_modules,__pycache__,.git")
|
ignore_patterns = options.get("--reload-ignore-patterns", "node_modules,__pycache__,.git")
|
||||||
ignore_patterns = ignore_patterns.split(',')
|
ignore_patterns = ignore_patterns.split(',')
|
||||||
print(ignore_patterns)
|
print("Ignoring Patterns %s" % ignore_patterns, flush=True)
|
||||||
|
|
||||||
|
# scandir utility functions
|
||||||
def _ignore(f):
|
def _ignore(f):
|
||||||
for ignore_pattern in ignore_patterns:
|
for ignore_pattern in ignore_patterns:
|
||||||
#if '__pycache__' in f or 'node_modules' in f:
|
#if '__pycache__' in f or 'node_modules' in f:
|
||||||
if ignore_pattern in f:
|
if ignore_pattern in f:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# individual os.path.mtime after glob is slow, so try using scandir
|
|
||||||
def get_files():
|
|
||||||
new_files = {} # path, mtime
|
|
||||||
# [f.stat().st_mtime for f in list(os.scandir('.'))]
|
|
||||||
new_files = _get_dir(directory, new_files)
|
|
||||||
print(new_files)
|
|
||||||
return new_files
|
|
||||||
|
|
||||||
def _get_dir(path, new_files):
|
def _get_dir(path, new_files):
|
||||||
print(path, flush=True)
|
|
||||||
for f_or_d in os.scandir(path):
|
for f_or_d in os.scandir(path):
|
||||||
if _ignore(f_or_d.path):
|
if _ignore(f_or_d.path):
|
||||||
continue
|
continue
|
||||||
|
@ -386,204 +361,47 @@ def _execute(args):
|
||||||
new_files[f_path] = f_or_d.stat().st_mtime
|
new_files[f_path] = f_or_d.stat().st_mtime
|
||||||
return new_files
|
return new_files
|
||||||
|
|
||||||
def get_files_glob_version_slow():
|
|
||||||
new_files = {} # path, mtime
|
|
||||||
print(f"getfiles1... {datetime.now()}", flush=True)
|
def get_files():
|
||||||
|
"""
|
||||||
for f in glob.glob(directory_glob, recursive=True):
|
os.scandir caches the file stats, call it recursively
|
||||||
if _ignore(f):
|
to emulate glob (which doesnt)
|
||||||
continue
|
"""
|
||||||
new_files[f] = os.path.getmtime(f)
|
new_files = {} # store path, mtime
|
||||||
print(f"getfiles2... {datetime.now()}", flush=True)
|
# [f.stat().st_mtime for f in list(os.scandir('.'))]
|
||||||
|
new_files = _get_dir(directory, new_files)
|
||||||
return new_files
|
return new_files
|
||||||
|
|
||||||
|
def do_check(prev_files):
|
||||||
|
""" Get files and their modified time and compare with previous times.
|
||||||
def do_check(prev_files, thread):
|
Restart the server if it has changed """
|
||||||
from datetime import datetime
|
|
||||||
print(f"Doing check... {datetime.now()}", flush=True)
|
|
||||||
new_files = get_files()
|
new_files = get_files()
|
||||||
print(f"got new files... {datetime.now()}", flush=True)
|
if len(new_files) > 50:
|
||||||
print(new_files)
|
print(f"{len(new_files)} files being watched", new_files)
|
||||||
|
|
||||||
if prev_files is not None and new_files != prev_files:
|
if prev_files is not None and new_files != prev_files:
|
||||||
# Call exit, the wrapper will restart the process
|
"""
|
||||||
print('Reload')
|
Call exit on current process, socketify App run method has a signal handler that will stop
|
||||||
logging.info("Reloading files...")
|
the uv/uwebsockets loop, then current process will exit and the cli execte() wrapper will then restart the process
|
||||||
reload_state.reload_pending = True #signal for Exeute to know whether it is a real SIGTERM or our own
|
|
||||||
print('running sigill')
|
|
||||||
import signal, sys
|
|
||||||
signal.raise_signal(signal.SIGTERM)
|
|
||||||
# os.kill(os.getpid(), RELOAD_SIGNAL) #doesnt work windows # call sigusr1 back on main thread which is caught by App.run()
|
|
||||||
#if sys.platform == 'win32':
|
|
||||||
# os.kill(os.getpid(),signal.SIGINT) # call sigint back on main thread which is caught by App.run()
|
|
||||||
"""print('running sysexit')
|
|
||||||
import sys
|
|
||||||
sys.exit(0)
|
|
||||||
print('ran sysexit')
|
|
||||||
"""
|
"""
|
||||||
|
print('Reloading...')
|
||||||
|
reload_state.reload_pending = True #signal for Exeute to know whether it is a real external SIGTERM or our own
|
||||||
|
import signal, sys
|
||||||
|
signal.raise_signal(signal.SIGTERM) # sigterm works on windows and posix
|
||||||
|
|
||||||
return new_files, thread
|
return new_files
|
||||||
|
|
||||||
|
|
||||||
print('rnt 0')
|
|
||||||
def poll_check():
|
def poll_check():
|
||||||
thread = None
|
|
||||||
# poll_frequency = 1
|
|
||||||
files = None
|
files = None
|
||||||
while True:
|
while True:
|
||||||
#print('polling fs', flush=True)
|
|
||||||
import time
|
|
||||||
time.sleep(poll_frequency)
|
time.sleep(poll_frequency)
|
||||||
files, thread = do_check(files, thread)
|
files = do_check(files)
|
||||||
#await asyncio.wait_for(thread, poll_frequency)
|
|
||||||
thread = None
|
|
||||||
# thread = threading.Thread(target=run_method, kwargs={from_main_thread': False}, daemon=True)
|
|
||||||
thread = threading.Thread(target=poll_check, kwargs={}, daemon=True)
|
thread = threading.Thread(target=poll_check, kwargs={}, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
run_method()
|
run_method()
|
||||||
"""
|
|
||||||
async def launch_with_file_probe(run_method, user_module_function, loop, poll_frequency=0.5):
|
|
||||||
import asyncio
|
|
||||||
import importlib.util
|
|
||||||
directory = os.path.dirname(importlib.util.find_spec(user_module_function.__module__).origin)
|
|
||||||
logging.info("Watching %s" % directory)
|
|
||||||
|
|
||||||
def get_files():
|
|
||||||
new_files = {} # path, mtime
|
|
||||||
for f in glob.glob(directory):
|
|
||||||
if '__pycache__' in f:
|
|
||||||
continue
|
|
||||||
new_files[f] = os.path.getmtime(f)
|
|
||||||
return new_files
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run_new_thread(thread):
|
|
||||||
#try:
|
|
||||||
# loop = asyncio.get_running_loop()
|
|
||||||
#except Exception:
|
|
||||||
# loop = asyncio.new_event_loop()
|
|
||||||
#if loop and thread:
|
|
||||||
# loop.stop()
|
|
||||||
# await loop.shutdown_default_executor()
|
|
||||||
async def arun():
|
|
||||||
run_method(from_main_thread=False)
|
|
||||||
# new_task = loop.create_task(arun())
|
|
||||||
loop.call_later(delay=1, callback=check_loop, loop)
|
|
||||||
new_task = loop.run_once(arun())
|
|
||||||
print(type(new_task))
|
|
||||||
print(dir(new_task))
|
|
||||||
print('new thread')
|
|
||||||
return new_task
|
|
||||||
#new_thread = threading.Thread(target=run_method, args=[], kwargs={'from_main_thread': False}, daemon=True)
|
|
||||||
#new_thread.start()
|
|
||||||
#return new_thread
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def do_check(prev_files, thread):
|
|
||||||
new_files = get_files()
|
|
||||||
|
|
||||||
if prev_files is not None and new_files != prev_files:
|
|
||||||
thread.cancel()
|
|
||||||
try:
|
|
||||||
await thread
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
print('reload')
|
|
||||||
logging.info("Reloading files...")
|
|
||||||
thread = run_new_thread(thread)
|
|
||||||
|
|
||||||
return new_files, thread
|
|
||||||
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
print('rnt 0')
|
|
||||||
thread = run_new_thread(None)
|
|
||||||
files = None
|
|
||||||
poll_frequency = 0.5
|
|
||||||
while True:
|
|
||||||
import time
|
|
||||||
print('awaiting')
|
|
||||||
# time.sleep(poll_frequency)
|
|
||||||
done, pending = await asyncio.wait([thread], timeout=poll_frequency)
|
|
||||||
print(pending, done)
|
|
||||||
#await asyncio.sleep(poll_frequency)
|
|
||||||
files, thread = await do_check(files, thread)
|
|
||||||
#await asyncio.wait_for(thread, poll_frequency)
|
|
||||||
# file watcher
|
|
||||||
async def launch_with_file_probe(run_method, user_module_function, loop, poll_frequency=0.5):
|
|
||||||
import asyncio
|
|
||||||
import importlib.util
|
|
||||||
directory = os.path.dirname(importlib.util.find_spec(user_module_function.__module__).origin)
|
|
||||||
logging.info("Watching %s" % directory)
|
|
||||||
|
|
||||||
def get_files():
|
|
||||||
new_files = {} # path, mtime
|
|
||||||
for f in glob.glob(directory):
|
|
||||||
if '__pycache__' in f:
|
|
||||||
continue
|
|
||||||
new_files[f] = os.path.getmtime(f)
|
|
||||||
return new_files
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run_new_thread(thread):
|
|
||||||
#try:
|
|
||||||
# loop = asyncio.get_running_loop()
|
|
||||||
#except Exception:
|
|
||||||
# loop = asyncio.new_event_loop()
|
|
||||||
#if loop and thread:
|
|
||||||
# loop.stop()
|
|
||||||
# await loop.shutdown_default_executor()
|
|
||||||
async def arun():
|
|
||||||
run_method(from_main_thread=False)
|
|
||||||
# new_task = loop.create_task(arun())
|
|
||||||
loop.call_later(delay=1, callback=check_loop, loop)
|
|
||||||
new_task = loop.run_once(arun())
|
|
||||||
print(type(new_task))
|
|
||||||
print(dir(new_task))
|
|
||||||
print('new thread')
|
|
||||||
return new_task
|
|
||||||
#new_thread = threading.Thread(target=run_method, args=[], kwargs={'from_main_thread': False}, daemon=True)
|
|
||||||
#new_thread.start()
|
|
||||||
#return new_thread
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def do_check(prev_files, thread):
|
|
||||||
new_files = get_files()
|
|
||||||
|
|
||||||
if prev_files is not None and new_files != prev_files:
|
|
||||||
thread.cancel()
|
|
||||||
try:
|
|
||||||
await thread
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
print('reload')
|
|
||||||
logging.info("Reloading files...")
|
|
||||||
thread = run_new_thread(thread)
|
|
||||||
|
|
||||||
return new_files, thread
|
|
||||||
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
print('rnt 0')
|
|
||||||
thread = run_new_thread(None)
|
|
||||||
files = None
|
|
||||||
poll_frequency = 0.5
|
|
||||||
while True:
|
|
||||||
import time
|
|
||||||
print('awaiting')
|
|
||||||
# time.sleep(poll_frequency)
|
|
||||||
done, pending = await asyncio.wait([thread], timeout=poll_frequency)
|
|
||||||
print(pending, done)
|
|
||||||
#await asyncio.sleep(poll_frequency)
|
|
||||||
files, thread = await do_check(files, thread)
|
|
||||||
#await asyncio.wait_for(thread, poll_frequency)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -611,8 +429,6 @@ def _execute(args):
|
||||||
if auto_reload:
|
if auto_reload:
|
||||||
# there's watchfiles module but socketify currently has no external dependencies so
|
# there's watchfiles module but socketify currently has no external dependencies so
|
||||||
# we'll roll our own for now...
|
# we'll roll our own for now...
|
||||||
# from watchfiles import arun_process
|
|
||||||
logging.info(' LAUNCHING WITH RELOAD ')
|
|
||||||
print(' LAUNCHING WITH RELOAD ', flush=True)
|
print(' LAUNCHING WITH RELOAD ', flush=True)
|
||||||
launch_with_file_probe(fork_app.run, module, fork_app.loop)
|
launch_with_file_probe(fork_app.run, module, fork_app.loop)
|
||||||
else: # run normally
|
else: # run normally
|
||||||
|
|
Ładowanie…
Reference in New Issue