swh:1:snp:7ce5f1105410d5ee1ad6abfdc873986c25b579e5
Tip revision: 50d9a883603a715b52901ba0e185b67918bc4654 authored by Dirk Roorda on 11 October 2018, 06:08:17 UTC
small fix
small fix
Tip revision: 50d9a88
start.py
import sys
import os
from platform import system
import psutil
import webbrowser
from time import sleep
from subprocess import PIPE, Popen
from tf.fabric import NAME, VERSION
from tf.server.common import getParam, getDebug, getNoweb, getDocker, getConfig, getLocalClones
from tf.server.kernel import TF_DONE, TF_ERROR
HELP = '''
USAGE
text-fabric --help
text-fabric --version
text-fabric datasource
text-fabric [-lgc] [-d] [-noweb] [-docker] datasource
text-fabric -k
text-fabric -k datasource
EFFECT
If a datasource is given and the -k flag is not passed,
a TF kernel for that data source is started.
When the TF kernel is ready, a webserver is started
serving a local website that exposes the data through
a query interface.
It will open in the default browser.
DATA LOADING
Text-Fabric looks for data in ~/text-fabric-data.
If data is not found there, it first downloads the relevant data from
github.
-lgc Look for data first in local github clones under ~/github.
If data is not found there, get data in the normal way.
MISCELLANEOUS
-d Debug mode. For developers of Text-Fabric itself.
The webserver reloads when its code changes.
The webserver is a bottle instance, started with reload=True.
-noweb Do not start the web browser
-docker Assume you are on docker. The web server is passed 0.0.0.0 as host.
CLEAN UP
If you press Ctrl-C the webserver is stopped, and after that the TF kernel
as well.
Normally, you do not have to do any clean up.
But if the termination is done in an irregular way, you may end up with
stray processes.
-k Kill mode. If a data source is given, the TF kernel and webserver for that
data source are killed.
Without a data source, all web-interface related processes are killed.
'''
FLAGS = set('''
-d
-lgc
-noweb
-docker
'''.strip().split())
BANNER = f'This is {NAME} {VERSION}'
def filterProcess(proc):
procName = proc.info['name']
commandName = '' if procName is None else procName.lower()
# commandName = proc.info['name'].lower()
found = False
kind = None
dataSource = None
trigger = 'python'
if commandName.endswith(trigger) or commandName.endswith(f'{trigger}.exe'):
good = False
parts = proc.cmdline()
for part in parts:
part = part.lower()
if part in FLAGS:
continue
trigger = 'text-fabric'
if part.endswith(trigger) or part.endswith(f'{trigger}.exe'):
kind = 'tf'
continue
if part == 'tf.server.kernel':
kind = 'data'
good = True
continue
if part == 'tf.server.web':
kind = 'web'
good = True
continue
if part.endswith('web.py'):
kind = 'web'
good = True
continue
if kind in {'data', 'web', 'tf'}:
if kind == 'tf' and part == '-k':
break
dataSource = part
good = True
break
if good:
found = True
if found:
return (kind, dataSource)
return False
def killProcesses(dataSource, kill=False):
tfProcs = {}
for proc in psutil.process_iter(attrs=['pid', 'name']):
test = filterProcess(proc)
if test:
(kind, ds) = test
tfProcs.setdefault(ds, {}).setdefault(kind, []).append(proc.info['pid'])
item = 'killed' if kill else 'terminated'
myself = os.getpid()
for (ds, kinds) in tfProcs.items():
if dataSource is None or ds == dataSource:
for kind in ('web', 'data', 'tf'):
pids = kinds.get(kind, [])
for pid in pids:
if pid == myself:
continue
try:
proc = psutil.Process(pid=pid)
if kill:
proc.kill()
else:
proc.terminate()
print(f'Process {kind} server for {ds}: {item}')
except psutil.NoSuchProcess:
print(f'Process {kind} server for {ds}: already {item}')
def getKill(cargs=sys.argv):
for arg in cargs[1:]:
if arg == '-k':
return True
return False
def main(cargs=sys.argv):
print(BANNER)
if len(cargs) >= 2 and any(arg in {'--help', '-help', '-h', '?', '-?'} for arg in cargs[1:]):
print(HELP)
return
if len(cargs) >= 2 and any(arg in {'--version', '-version', '-v'} for arg in cargs[1:]):
return
isWin = system().lower().startswith('win')
kill = getKill(cargs=cargs)
if kill:
dataSource = getParam(cargs=cargs, interactive=False)
if dataSource is False:
return
killProcesses(dataSource)
return
dataSource = getParam(cargs=cargs, interactive=True)
noweb = getNoweb(cargs=cargs)
ddataSource = ('-d', dataSource) if getDebug(cargs=cargs) else (dataSource,)
ddataSource = ('-docker', *ddataSource) if getDocker(cargs=cargs) else ddataSource
ddataSource = ('-lgc', *ddataSource) if getLocalClones(cargs=cargs) else ddataSource
kdataSource = ('-lgc', dataSource) if getLocalClones(cargs=cargs) else (dataSource,)
if dataSource is not None:
config = getConfig(dataSource)
pKernel = None
pWeb = None
if config is not None:
print(f'Cleaning up remnant processes, if any ...')
killProcesses(dataSource, kill=True)
pythonExe = 'python' if isWin else 'python3'
pKernel = Popen(
[pythonExe, '-m', 'tf.server.kernel', *kdataSource],
stdout=PIPE, bufsize=1, encoding='utf-8',
)
print(f'Loading data for {dataSource}. Please wait ...')
for line in pKernel.stdout:
sys.stdout.write(line)
if line.rstrip() == TF_ERROR:
return
if line.rstrip() == TF_DONE:
break
sleep(1)
pWeb = Popen(
[pythonExe, '-m', 'tf.server.web', *ddataSource],
bufsize=0,
)
if not noweb:
sleep(1)
print(f'Opening {dataSource} in browser')
webbrowser.open(
f'{config.protocol}{config.host}:{config.webport}',
new=2,
autoraise=True,
)
if pWeb and pKernel:
try:
for line in pKernel.stdout:
sys.stdout.write(line)
except KeyboardInterrupt:
print('')
if pWeb:
pWeb.terminate()
print('TF webserver has stopped')
if pKernel:
pKernel.terminate()
for line in pKernel.stdout:
sys.stdout.write(line)
print('TF kernel has stopped')
if __name__ == "__main__":
main()