Source code for gather.entry

"""
Abstractions for writing entrypoints.

This is meant to reduce the overhead when writing a command with
subcommands.

In the example below,
the assumption is that your code is in the package
``awesomeawesome``.

In
``awesomeawesome/__init__.py``:

.. code::

    from gather import entry
    ENTRY_DATA = entry.EntryData.create(__name__)

In
``__main__.py``:

.. code::

    from gather import entry
    from . import ENTRY_DATA

    entry.dunder_main(
        globals_dct=globals(),
        command_data=ENTRY_DATA,
    )

Registering a new subcommand is done by adding the following to,
say,
``awesomeawesome/commands.py``:

.. code::

    from gather.commands import add_argument
    from . import ENTRY_DATA
    from commander_data import COMMAND


    @ENTRY_DATA.register()
    def hello(args):
        LOGGER.info("Hello world")


    @ENTRY_DATA.register(
        add_argument("--no-dry-run"),
        add_argument("--a-thing", default="the-thing"),
    )
    def frobnicate(args):
        args.run(
            COMMAND.rm(recursive=None, force=None)
        ) # Will only run with --no-dry-run
        hello = args.safe_run(
            COMMAND.echo("hello")
        ).stdout.strip() # Will run regardless

Note that commands can be added in any file,
as long as they are registered properly.

Optionally,
you can add script entry points
in
`pyproject.toml`:

.. code::

    [project.scripts]
    awesomeawesomectl = "awesomeawesome:ENTRY_DATA.main_command"
    frobnicate = "awesome:ENTRY_DATA.sub_command"

In that case,
the following will work:

* ``python -m awesomeawesome hello``
* ``awesomeawesome hello``
* ``python -m awesomeawesome frobnicate``
* ``awesomeawesome frobnicate``
* ``frobincate``

"""

from __future__ import annotations
import functools
import logging
import runpy
import sys
from typing import Callable

import attrs
import toolz

from . import commands as commandslib, api


[docs] def dunder_main(globals_dct, command_data, logger=logging.getLogger()): """ Call from ``__main__`` """ if globals_dct["__name__"] != "__main__": raise ImportError("module cannot be imported", globals_dct["__name__"]) ch = logging.StreamHandler() ch.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s:%(levelname)s:%(name)s:%(message)s") ch.setFormatter(formatter) logger.addHandler(ch) logger.setLevel(logging.INFO) commandslib.run_maybe_dry( parser=commandslib.set_parser(collected=command_data.collector.collect()), is_subcommand=globals_dct.get("IS_SUBCOMMAND", False), prefix=command_data.prefix, argv=sys.argv, )
def _noop(_ignored): # pragma: no cover pass
[docs] @attrs.frozen class EntryData: """ Data for the entry point. """ prefix: str collector: api.Collector register: Callable main_command: Callable[[], None] sub_command: Callable[[], None]
[docs] @classmethod def create(cls, package_name, prefix=None): """ Create a new instance from package_name and prefix """ if prefix is None: prefix = package_name collector = api.Collector() register = commandslib.make_command_register(collector) main_command = toolz.compose( _noop, functools.partial( runpy.run_module, package_name, run_name="__main__", ), ) sub_command = functools.partial( main_command, init_globals=dict(IS_SUBCOMMAND=True) ) return cls( prefix=prefix, collector=collector, register=register, main_command=main_command, sub_command=sub_command, )