Skip to content

Commit

Permalink
Merge pull request #44 from tangkong/enh_cli
Browse files Browse the repository at this point in the history
ENH: Add cli framework
  • Loading branch information
tangkong authored Sep 19, 2024
2 parents b74af9a + c9381df commit 3b8b95b
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 0 deletions.
3 changes: 3 additions & 0 deletions beams/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .version import __version__ # noqa: F401

__all__ = []
3 changes: 3 additions & 0 deletions beams/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .bin.main import main

main()
3 changes: 3 additions & 0 deletions beams/bin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import main

__all__ = ["main"]
96 changes: 96 additions & 0 deletions beams/bin/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
`beams` is the top-level command for accessing various subcommands.
"""

import argparse
import importlib
import logging

import beams
from beams.logging import setup_logging

DESCRIPTION = __doc__


COMMAND_TO_MODULE = {
"run": "run",
}


def _try_import(module_name):
return importlib.import_module(f".{module_name}", "beams.bin")


def _build_commands():
global DESCRIPTION
result = {}
unavailable = []

for command, module_name in sorted(COMMAND_TO_MODULE.items()):
try:
module = _try_import(module_name)
except Exception as ex:
unavailable.append((command, ex))
else:
result[module_name] = (module.build_arg_parser, module.main)
DESCRIPTION += f'\n $ beams {command} --help'

if unavailable:
DESCRIPTION += '\n\n'

for command, ex in unavailable:
DESCRIPTION += (
f'\nWARNING: "beams {command}" is unavailable due to:'
f'\n\t{ex.__class__.__name__}: {ex}'
)

return result


COMMANDS = _build_commands()


def main():
top_parser = argparse.ArgumentParser(
prog='beams',
description=DESCRIPTION,
formatter_class=argparse.RawTextHelpFormatter
)

top_parser.add_argument(
'--version', '-V',
action='version',
version=beams.__version__,
help="Show the beams version number and exit."
)

top_parser.add_argument(
'--log', '-l', dest='log_level',
default='INFO',
type=str,
help='Python logging level (e.g. DEBUG, INFO, WARNING)'
)

subparsers = top_parser.add_subparsers(help='Possible subcommands')
for command_name, (build_func, main) in COMMANDS.items():
sub = subparsers.add_parser(command_name)
build_func(sub)
sub.set_defaults(func=main)

args = top_parser.parse_args()
kwargs = vars(args)
log_level = kwargs.pop('log_level')

setup_logging(log_level)
logger = logging.getLogger("beams")

if hasattr(args, 'func'):
func = kwargs.pop('func')
logger.debug('%s(**%r)', func.__name__, kwargs)
func(**kwargs)
else:
top_parser.print_help()


if __name__ == '__main__':
main()
32 changes: 32 additions & 0 deletions beams/bin/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
`beams run` runs a behavior tree given a configuration file
"""

from __future__ import annotations

import argparse
import logging

logger = logging.getLogger(__name__)


DESCRIPTION = __doc__


def build_arg_parser(argparser=None):
if argparser is None:
argparser = argparse.ArgumentParser()

argparser.description = DESCRIPTION
argparser.formatter_class = argparse.RawTextHelpFormatter

argparser.add_argument(
"filepath",
type=str,
help="Behavior Tree configuration filepath"
)


def main(*args, **kwargs):
from beams.bin.run_main import main
main(*args, **kwargs)
26 changes: 26 additions & 0 deletions beams/bin/run_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Logic for `superscore run` main
"""

import logging
from pathlib import Path

from beams.tree_config import get_tree_from_path

logger = logging.getLogger(__name__)


def main(filepath: str):
logger.info(f"Running behavior tree at {filepath}")
# grab config
fp = Path(filepath).resolve()
if not fp.is_file():
raise ValueError("Provided filepath is not a file")

tree = get_tree_from_path(fp)
print(tree)
# TODO: the rest of whatever we determine the "run" process to be
# run external server?
# setup tree?
# tick?
# settings for ticking? (continuous tick?, as separate process?)
1 change: 1 addition & 0 deletions beams/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def setup_logging(level: int = logging.INFO):

# set console debug level, log files are always DEBUG
config["handlers"]["console"]["level"] = level
config["loggers"]["beams"]["level"] = level
logging.config.dictConfig(config)

# setup main logger thread to listen to mp.Process loggers
Expand Down
2 changes: 2 additions & 0 deletions beams/logging.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
version: 1
disable_existing_loggers: False

formatters:
# For the console:
Expand Down Expand Up @@ -29,4 +30,5 @@ handlers:

loggers:
beams:
level: INFO
handlers: [console, debug]
23 changes: 23 additions & 0 deletions docs/source/upcoming_release_notes/44-enh_cli.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
44 enh_cli
##########

API Breaks
----------
- N/A

Features
--------
- Adds cli framework and entrypoint

Bugfixes
--------
- Configures logging for the base logger (non Process spawned loggers) and avoids disabling previously created loggers
- Fix package version handling

Maintenance
-----------
- N/A

Contributors
------------
- tangkong

0 comments on commit 3b8b95b

Please sign in to comment.