Finding the names (and callable objects) in the current script __main__
I was trying to create a standalone python script that used only the standard library to find functions in the current file with the prefix task_ to create a minimal homebrewed version of what npm run provides.
TL;DR
If you are running a standalone script you want to run inspect.getmembers on the module which is defined in sys.modules['__main__']. Eg,
inspect.getmembers(sys.modules['__main__'], inspect.isfunction)
But I wanted to filter the list of methods by prefix and strip the prefix to create a lookup dictionary.
def _inspect_tasks():
import inspect
return { f[0].replace('task_', ''): f[1]
for f in inspect.getmembers(sys.modules['__main__'], inspect.isfunction)
if f[0].startswith('task_')
}
Example Output:
{
'install': <function task_install at 0x105695940>,
'dev': <function task_dev at 0x105695b80>,
'test': <function task_test at 0x105695af0>
}
Longer Version
I wanted the names of the methods to define CLI task names without having to repeat myself.
./tasks.py
#!/usr/bin/env python3
import sys
from subprocess import run
def _inspect_tasks():
import inspect
return { f[0].replace('task_', ''): f[1]
for f in inspect.getmembers(sys.modules['__main__'], inspect.isfunction)
if f[0].startswith('task_')
}
def _cmd(command, args):
return run(command.split(" ") + args)
def task_install(args):
return _cmd("python3 -m pip install -r requirements.txt -r requirements-dev.txt --upgrade", args)
def task_test(args):
return _cmd("python3 -m pytest", args)
def task_dev(args):
return _cmd("uvicorn api.v1:app", args)
if __name__ == "__main__":
tasks = _inspect_tasks()
if len(sys.argv) >= 2 and sys.argv[1] in tasks.keys():
tasks[sys.argv[1]](sys.argv[2:])
else:
print(f"Must provide a task from the following: {list(tasks.keys())}")
Example no arguments:
λ ./tasks.py
Must provide a task from the following: ['install', 'dev', 'test']
Example running test with extra arguments:
λ ./tasks.py test -qq
s.ssss.sF..Fs.sssFsss..ssssFssFs....s.s
You get the point. As my projects get more and more involved, it's going to be easier to keep a script up to date than to keep the README up to date and I can abstract it down to just:
./tasks.py install
./tasks.py dev
./tasks.py test
./tasks.py publish
./tasks.py logs