Package: delfino
🧰  Delfino  🧰The Ultimate Command Line Companion for Your ProjectsTired of managing scattered scripts? Say goodbye to complexity with Delfino!Delfino is a powerful wrapper around Click, the popular command line interface package. It automatically discovers and executes Click commands in your project. But Delfino doesn't stop there - it takes it a step further by allowing you to create plugins, making script distribution and installation a breeze.Why choose Delfino?Streamline Scripts: Consolidate all your helper scripts into a single, easy-to-use entry point. No more hunting for scripts or dealing with convoluted aliases. Simply use delfino followed by the script name and options.Reusable Plugins: Package your helper scripts as plugins and install them with pip. Maintain consistency across projects and easily incorporate updates through a flexible configuration system.Simplify Tooling: Delfino extends Click with advanced features like pass-through command-line options and seamless handling of file lists. Say goodbye to verbosity and hello to optimized workflows.Don't let scattered scripts and complex tooling slow you down. Embrace Delfino and revolutionize your command line experience. Try Delfino today and unlock simplicity in your projects!Table of contentInstallationConfigurationUsageDevelopmentCommands discoveryMinimal commandMinimal pluginExisting pluginsEnabling a pluginEnabling/disabling commandsAdvanced usageAuto-completionRunning external programsOptional dependenciesProject settingsPlugin settingsProject specific overridesGrouping commandsInstallationpip:pip install delfinoPoetry:poetry add --group=dev delfinoPipenv:pipenv install -d delfinoorpip:pip install delfino[completion]Poetry:poetry add --group=dev delfino[completion]Pipenv:pipenv install -d delfino[completion]to enableauto-completion.ConfigurationAll configuration is expected to live in one of the following files:pyproject.tomlin the project root.delfinorcin the project root - to allow dev specific config, not source controlled or for non-Python projects.delfinorcin the user home directory - for user tools available in the systemIf multiple files are discovered, only the highest one in the list will be used.The format for.delfinorcis the same as forpyproject.toml.UsageRundelfino --helpto see all available commands and their usage.DevelopmentDelfino is a simple wrapper aroundClick commands. Any Click command will be accepted by Delfino.Commands discoveryDelfino looks for anyclick.Commandsub-class in the following locations:commandsfolder in the root of the project (next to thepyproject.tomlfile). This location is useful for commands that don't need to be replicated in multiple locations/projects. To change the default location, use thetool.delfino.local_command_foldersconfig option. It allows specifying more than one folder.python module import path (<IMPORT_PATH>) specified byentry_pointofa plugin:[tool.poetry.plugins]# Optional super table[tool.poetry.plugins."delfino.plugin"]"delfino-<PLUGIN_NAME>"="<IMPORT_PATH>"Folder specified in theconfig fileundertool.delfino.local_commands_directory.Any files starting with an underscore, except for__init__.py, will be ignored.WarningFolders are NOT inspected recursively. If you place any commands into nested folders, they will not be loaded by Delfino.Minimal commandCreate acommandsfolder:mkdircommandsCreate acommands/__init__.pyfile, with the following content:importclick@click.command()defcommand_test():"""Tests commands placed in the `commands` folder are loaded."""print("✨ This command works! ✨")See if Delfino loads the command. Open a terminal and in the root of the project, call:delfino --help. You should see something like this:Usage: delfino [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  ...
  command-test            Tests commands placed in the `commands` folder...
  ...Run the command withdelfino command-testMinimal pluginIf you'd like to use one or more commands in multiple places, you can create a plugin. A plugin is just a regular Python package with specific entry point telling Delfino it should use it. It can also be distributed as any other Python packages, for example via Pypi.The quickest way to create one is to use aDelfino plugin cookiecutter template, which asks you several questions and sets up the whole project.Alternatively, you can get inspired bythe demo pluginor any of the otherexisting plugins.Existing pluginsPlugins can greatly reduce code duplication and/or promote your own standards in multiple places. For example, you can create a plugin wrapping common linting tools that you use on your projects, including their default configuration. Keeping the rules and creating new projects with the same style suddenly becomes a matter of installing one Python library.Each plugin can contain one or more Click commands that are automatically discovered and exposed by Delfino. Seedelfino-demofor a minimal plugin, which provide ademocommand printing out a message.Existing plugins:Plugin nameDescriptiondelfino-demoA minimal plugin example for Delfino. Contains one command printing a message.delfino-coreCommands wrapping tools used during every day development (linting, testing, dependencies update).delfino-dockerDocker build helper script.Enabling a pluginFor security reasons, plugins are disabled by default. To enable a plugin, you have to include it in thepyproject.tomlfile:[tool.delfino.plugins.<PLUGIN_NAME>]Enabling/disabling commandsBy default, all commands are enabled. Useenable_commandsordisable_commandsto show only a subset of commands. If both used, disabled commands are subtracted from the set of enabled commands.# [tool.delfino.plugins.<PLUGIN_NAME_A>]# enable_commands = [<COMMAND_NAME>]# disable_commands = [<COMMAND_NAME>]# [tool.delfino.plugins.<PLUGIN_NAME_B>]# enable_commands = [<COMMAND_NAME>]# disable_commands = [<COMMAND_NAME>]Advanced usageAuto-completionYou can either attempt to install completions automatically with:delfino--install-completionor generate it with:delfino--show-completionand manually put it in the relevant RC file.The auto-completion implementation is dynamic so that every time it is invoked, it uses the current project. Each project can have different commands or disable certain commands it doesn't use. And dynamic auto-completion makes sure only the currently available commands will be suggested.The downside of this approach is that evaluating what is available each time is slower than a static list of commands.Running external programsIt is up to you how you want to execute external processes as part of commands (if you need to at all). A common way in Python is to usesubprocess.run. Delfino comes with its ownrunimplementation, which wraps and simplifiessubprocess.runfor the most common use cases:Normalizingsubprocess.runarguments - you can pass in either a string or a list. Either way,subprocess.runwill be executed correctly.Handling errors from the execution via theon_errorargument. Giving the option to either ignore the errors and continue (PASS), not continue and clean exit (EXIT) or not continue and abort with error code (ABORT).Setting environment variables.Logging what is being executed in the debug level.Example:# commands/__init__.pyimportclickfromdelfino.executionimportrun,OnError@click.command()deftest():run("pytest tests",on_error=OnError.ABORT)Optional dependenciesIf you put several commands into oneplugin, you can make some dependencies of some commandsoptional. This is useful when a command is not always used, and you don't want to install unnecessary dependencies. Instead, you can check if a dependency is installed only when the command is executed withdelfino.validation.assert_pip_package_installed:# commands/__init__.pyimportclickfromdelfino.validationimportassert_pip_package_installedtry:fromgitimportRepoexceptImportError:pass@click.command()defgit_active_branch():assert_pip_package_installed("gitpython")print(Repo(".").active_branch)In the example above, ifgitpythonis not installed, delfino will show the command but will fail with suggestion to installgitpythononly when the command is executed. You can also addgit_active_branchintodisable_commandsconfigin places where you don't intend to use it.This way you can greatly reduce the number of dependencies a plugin brings into a project without a need to have many small plugins.Project settingsYou can store an arbitrary object in the Click context asclick.Context.obj. Delfino utilizes this object to store an instance ofAppContext, which provides access to project related information. If you need to, you can still attach arbitrary attributes to this object later.You can pass this object to your commands by decorating them withclick.pass_obj:# commands/__init__.pyimportclickfromdelfino.models.app_contextimportAppContext@click.command()@click.pass_objdefprint_app_version(obj:AppContext):print(obj.pyproject_toml.tool.poetry.version)Plugin settingsPlugin settings are expected to live in thepyproject.tomlfile. To prevent naming conflicts, each plugin must put its settings undertool.delfino.plugins.<PLUGIN_NAME>. It also allows Delfino to pass these settings directly to commands from these plugins.Delfino loads, parses, validates and stores plugin settings inAppContext.plugin_config. If not specified otherwise (see below), it will be an instance ofPluginConfig, with any extra keys unvalidated and in JSON-like Python objects.You can add additional validation to your plugin settings by sub-classing thePluginConfig, defining expected keys, default values and/or validation. Delfino utilizespydanticto create data classes.Delfino also needs to know, which class to use for the validation. To do that, switch todelfino.decorators.pass_app_contextinstead ofclick.pass_obj:# pyproject.toml[tool.delfino.plugins.delfino_login_plugin]username="user"# commands/__init__.pyimportclickfromdelfino.models.pyproject_tomlimportPluginConfigfromdelfino.models.app_contextimportAppContextfromdelfino.decoratorsimportpass_app_contextclassLoginPluginConfig(PluginConfig):login:str@click.command()@pass_app_context(LoginPluginConfig)deflogin(app_context:AppContext[LoginPluginConfig]):print(app_context.plugin_config.login)TheAppContextclass is generic. Defining thePluginConfigType(such asAppContext[LoginPluginConfig]in the example above) enables introspection and type checks.Project specific overridesIt is likely your projects will require slight divergence to the defaults you encode in your scripts. The following sections cover the most common use cases.Pass-through argumentsYou can pass additional arguments to downstream tools by decorating commands with thedecorators.pass_argsdecorator:# commands/__init__.pyfromtypingimportTupleimportclickfromdelfino.decoratorsimportpass_argsfromdelfino.executionimportrun,OnError@click.command()@pass_argsdeftest(passed_args:Tuple[str,...]):run(["pytest","tests",*passed_args],on_error=OnError.ABORT)Then additional arguments can be passed either via command line after--:delfinotest----capture=noOr via configuration in thepyproject.tomlfile:[tool.delfino.plugins.<PLUGIN>.test]pass_args=['--capture=no']Either way, both will result in executingpytest tests --capture=no.Files overrideYou can override files passed to downstream tools by decorating commands with thedecorators.files_folders_optiondecorator:# commands/__init__.pyfromtypingimportTupleimportclickfromdelfino.decoratorsimportfiles_folders_optionfromdelfino.executionimportrun,OnError@click.command()@files_folders_optiondeftest(files_folders:Tuple[str,...]):ifnotfiles_folders:files_folders=("tests/unit","tests/integration")run(["pytest",*files_folders],on_error=OnError.ABORT)Then the default"tests/unit", "tests/integration"folders can be overridden either via command line options-f/--file/--folder:delfinotest-ftests/otherOr via configuration in thepyproject.tomlfile:[tool.delfino.plugins.<PLUGIN>.test]files_folders=['tests/other']Either way, both will result in executingpytest tests/other.Grouping commandsOften it is useful to run several commands as a group with a different command name. Click supports calling other commands withclick.Context.forwardorclick.Context.invoke.