Is this a circular dependency in Python? - python

I'm making a command line interface in Python.
The class named Application holds the command classes derived from the command base in a list. The problem is that one of the commands (help command) needs a list of all the other commands. And because of this, if I access the list in the application class, where I include the command classes derived from "CommandBase" class, from within a command class, will there be a circular dependecy ?
Here is the very simplified version of my code:
class Application:
commands: list[CommandBase]
def add_command(self, cmd):
self.commands.append(cmd)
def main_loop(self):
# execute commands according to user input
while True:
readln = input("> ")
# find command by the name
command.execute()
class CommandBase:
def get_name() -> str:
pass
class CommandHelp(CommandBase):
def __init__(self, app):
self.app = app
def get_name():
return "help"
def execute(self):
# print command list
print(",".join(self.app.commands))
# create instances
app = Application()
random_cmd = CommandRandom()
help_cmd = CommandHelp()
# add commands
app.add_command(random_cmd)
app.add_command(help_cmd)

Related

How to create multiple CLI options identified my package name in a python?

I want to build a cli interface for my application which has nested functionality. Example:
├── ...
├── setup.py
└── package-name
├──__init__.py
├──command1.py
└──command2.py
package-name command1 --arg .. ..
package-name command2 --arg ..
AND
python -m package-name command1 --arg ..
The thing to note here is that command1 and command2 are independent modules that accept different command-line args. So linking them together in a __main__.py might also be another challenge.
I came across similar questions that use entry_points in setup.py to create similar cli functionality but its not exactly what I'm looking for. I found this similar question.
How to create a CLI in Python that can be installed with PIP?
If you want access multiple sub-cli's in one entry command, you can implement a sub-command manager at __main__.py, which can parse sub-command from sys.argv and then dispatch to target module.
1️⃣First, i recommend the google fire, which can satify you in most scenarios without extra code.
Here is a example, you can replace the add/multiply function to your sub-command function by using from command1 import xx,and use entry points to expose the main function.
import fire
def add(x, y):
return x + y
def multiply(x, y):
return x * y
def main():
fire.Fire({
'add': add,
'multiply': multiply,
})
if __name__ == '__main__':
main()
We can debug this in the same way as below:
$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
2️⃣Second, if you need implements by your self for some purpose, such as using argparse to define options for each command. A typical practice is the Django command, the official demo: Writing custom django-admin commands
The core steps is :
define a BaseCommand
implements BaseCommand in sub commands.py, and named it as Command.
__main__.py implements find command and call
# base_command.py
class BaseCommand(object):
def create_parser(self, prog_name, subcommand, **kwargs):
"""
Create and return the ``ArgumentParser`` which will be used to
parse the arguments to this command.
"""
# TODO: create your ArgumentParser
return CommandParser(**kwargs)
def add_arguments(self, parser):
"""
Entry point for subclassed commands to add custom arguments.
"""
pass
def run_from_argv(self, argv):
"""
Entry point for commands to be run from the command line.
"""
parser = self.create_parser(argv[0], argv[1])
options = parser.parse_args(argv[2:])
cmd_options = vars(options)
args = cmd_options.pop('args', ())
self.handle(*args, **cmd_options)
def handle(self, *args, **options):
"""
The actual logic of the command. Subclasses must implement
this method.
"""
raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
# command1.py
class Command(BaseCommand):
def handle(self, *args, **options):
print("Hello, it is command1!")
# command2.py
class Command(BaseCommand):
def handle(self, *args, **options):
print("Hello, it is command2!")
# __main__.py
def main():
# sub command name is the second argument
command_name = sys.argv[1]
# build sub command module name, and import
# package-name is the module name you mentioned
cmd_package_name = f'package-name.{command_name}'
instance = importlib.import_module(cmd_package_name)
# create instance of sub command, the Command must exist
command = instance.Command()
command.run_from_argv(sys.argv)
if __name__ == "__main__":
main()

How to run function if the command is the name of a function?

So I'm wondering how to run a function from another file so if the command is the same as the function name?
discord.py implements it exactly how I want to do it. where it can just call the commands.
my code looks like this
main.py
from ai_main import CommandManager
bot = CommandManager(prefix="!")
#bot.call_fun
def hello():
print("hello")
bot.run()
ai_main.py
class CommandManager:
def __init__(self, prefix="!"):
print("INIT")
self.prefix = prefix
def run(self):
user_input = input("Enter command: ")
self.check_for_command(user_input)
def check_for_command(self, user_input):
if user_input[0] == self.prefix:
string = ""
for char in user_input[1:]:
if char == " ":
break
else:
string += char
if string != "":
self.call_fun(string)
def call_fun(self, command):
# command.run() ??? how to call it?
return command
You want to store an instance of the function inside a mapping as part of the CommandManager class. Later, you can use the mapping to find the stored function.
You can also make the run method use a While loop to allow calling multiple commands.
class CommandManager:
def __init__(self, prefix="!"):
"""
The CommandManager allows users to register functions for
calling them in an interactive shell!
Each command can be registered in the CommandManager by wrapping
it with the add_command decorator. This will store the function in
the manager by looking at it's __name__ attribute and later allow
users to reference the function by that name.
All commands given to the CommandManager must start with "!",
followed by the name of the registered function.
"""
self.prefix = prefix
self.commands = {}
def run(self):
"""
Run the CommandManager repeatedly by asking for user input.
"""
while True:
user_input = input("Enter command: ")
self.run_command(user_input)
def run_command(self, user_input):
"""
Parses input given by the user to determine an appropriate
command to call. Commands may only be run one at a time and
must start with "!"
"""
user_input = user_input.strip()
if user_input[0] != self.prefix:
print(f"Invalid command: '{user_input}'. Must start with '!'")
return
# Strip out the prifix and use the first word as the command name
command_name = user_input[1:].strip().split(" ")[0]
# Check that we have a command registered for that name.
if command_name not in self.commands:
print(f"Unknown command: '{user_input}'.")
return
self.commands[command_name]()
def add_command(self, command):
"""
Register the command to the manager. Uses the __name__ attribute of
the function to store a reference to the function.
"""
self.commands[command.__name__] = command
return command
from ai_main import CommandManager
bot = CommandManager()
#bot.add_command
def hello():
print("hello")
if __name__ == "__main__":
bot.run()

How to integrate python scripting in my python code

I am writing Python code (based on PyQt5 signals and slots). I need to make my code "Scriptable". By scriptable I mean the user can utilize the internal objects himself in a user-defined python script to develop/automate some functions,..etc. But I have no idea of how to implement it clearly.
I have tried using (exec) function in python in the following way:
user-def.py
def script_entry(main_object_in_my_code):
# Connecting signal from main_object_in_my_code (inherited from QObject) to other functions in this
# file. example:
main_object_in_my_code.event_1.connect(function_1)
#QtCore.pyqtSlot(str)
def function_1 (args):
#do user-defined logic using those args.
then in my script when user want to execute it, he inputs (as example)
source user-def.py
the main script reads the script and uses exec as the following:
with open(script_path) as f:
script = f.read()
exec(script, globals())
the problem is that events are triggered but function function_1 is not executed.
I am sure this is not the right way to do this. So, How can I implement my code to be (scriptable) using user defined scripts?
I would recomend to create a class and extend from it, let the 'user' call the functions when s/he needs.
If you are not in touch with class inheritance check this tutorial
source_def.py
class Main:
def __init__(self):
super(Main, self).__init__()
def script_entry(self, main_object_in_my_code):
main_object_in_my_code.event_1.connect( function_1 )
#QtCore.pyqtSlot(str)
def function_1( self, args ):
#this checks if the function is set
invert_op = getattr(self, "user_function", None)
if callable(user_function):
eval('self.user_function( args )')
user_def.py
from source_def import Main
class UserClass( Main ):
def __init__(self):
super(UserClass, self).__init__()
def user_function(self , args ):
print( args )
Try this

Class variable query in python

Framework: Robot, Language: Python-3.7.1 Proficiency: Novice
I have a variable args=[] defined at class level. The values of the variables are being assigned from command prompt using module 'sys'
import sys
class Runner():
args = []
def argument_reader(self):
self.args = list(sys.argv)
def login(self):
return self.args[1], self.args[2]
I could print all the values of args as long as execution stays within the module. If I wanted to call the same values from other module, it does not return anything as the values are being cleared out from the memory. Since class variables are static by default in python, why system is not RETAINING the values?
cmd line>>py Runner.py testing test#123
For Example:
Calling method from same class:-
run = Runner()
run.argument_reader()
print(run.login())
Output>>> testing, testing#123
Calling the same method from another class:-
runn = Runner.Runner()
print(runn.login())
output>> IndexError: list index out of range
If you want a singleton-type value, change your code to look like this.
class Runner():
args = []
def argument_reader(self):
Runner.args = list(sys.argv)
def login(self):
return Runner.args[1], Runner.args[2]
Otherwise, you'll have to call argument_reader() on each instance.

How can I document click commands using Sphinx?

Click is a popular Python library for developing CLI applications with. Sphinx is a popular library for documenting Python packages with. One problem that some have faced is integrating these two tools so that they can generate Sphinx documentation for their click-based commands.
I ran into this problem recently. I decorated some of my functions with click.command and click.group, added docstrings to them and then generated HTML documentation for them using Sphinx's autodoc extension. What I found is that it omitted all documentation and argument descriptions for these commands because they had been converted into Command objects by the time autodoc got to them.
How can I modify my code to make the documentation for my commands available to both the end user when they run --help on the CLI, and also to people browsing the Sphinx-generated documentation?
You can use a sphinx extension sphinx-click for this now. It can generate docs for nested commands with options and arguments description. The output will be like when you run --help.
Usage
Install the extension
pip install sphinx-click
Enable the plugin in your Sphinx conf.py file:
extensions = ['sphinx_click.ext']
Use plugin wherever necessary in the documentation
.. click:: module:parser
:prog: hello-world
:show-nested:
Example
There is simple click application, which is defined in the hello_world module:
import click
#click.group()
def greet():
"""A sample command group."""
pass
#greet.command()
#click.argument('user', envvar='USER')
def hello(user):
"""Greet a user."""
click.echo('Hello %s' % user)
#greet.command()
def world():
"""Greet the world."""
click.echo('Hello world!')
For documenting all subcommands we will use code below with the :show-nested: option
.. click:: hello_world:greet
:prog: hello-world
:show-nested:
Before building docs make sure that your module and any additional dependencies are available in sys.path either by installing package with setuptools or by manually including it.
After building we will get this:
generated docs
More detailed information on various options available is provided in documentation of the extension
Decorating command containers
One possible solution to this problem that I've recently discovered and seems to work would be to start off defining a decorator that can be applied to classes. The idea is that the programmer would define commands as private members of a class, and the decorator creates a public function member of the class that's based on the command's callback. For example, a class Foo containing a command _bar would gain a new function bar (assuming Foo.bar does not already exist).
This operation leaves the original commands as they are, so it shouldn't break existing code. Because these commands are private, they should be omitted in generated documentation. The functions based on them, however, should show up in documentation on account of being public.
def ensure_cli_documentation(cls):
"""
Modify a class that may contain instances of :py:class:`click.BaseCommand`
to ensure that it can be properly documented (e.g. using tools such as Sphinx).
This function will only process commands that have private callbacks i.e. are
prefixed with underscores. It will associate a new function with the class based on
this callback but without the leading underscores. This should mean that generated
documentation ignores the command instances but includes documentation for the functions
based on them.
This function should be invoked on a class when it is imported in order to do its job. This
can be done by applying it as a decorator on the class.
:param cls: the class to operate on
:return: `cls`, after performing relevant modifications
"""
for attr_name, attr_value in dict(cls.__dict__).items():
if isinstance(attr_value, click.BaseCommand) and attr_name.startswith('_'):
cmd = attr_value
try:
# noinspection PyUnresolvedReferences
new_function = copy.deepcopy(cmd.callback)
except AttributeError:
continue
else:
new_function_name = attr_name.lstrip('_')
assert not hasattr(cls, new_function_name)
setattr(cls, new_function_name, new_function)
return cls
Avoiding issues with commands in classes
The reason that this solution assumes commands are inside classes is because that's how most of my commands are defined in the project I'm currently working on - I load most of my commands as plugins contained within subclasses of yapsy.IPlugin.IPlugin. If you want to define the callbacks for commands as class instance methods, you may run into a problem where click doesn't supply the self parameter to your command callbacks when you try to run your CLI. This can be solved by currying your callbacks, like below:
class Foo:
def _curry_instance_command_callbacks(self, cmd: click.BaseCommand):
if isinstance(cmd, click.Group):
commands = [self._curry_instance_command_callbacks(c) for c in cmd.commands.values()]
cmd.commands = {}
for subcommand in commands:
cmd.add_command(subcommand)
try:
if cmd.callback:
cmd.callback = partial(cmd.callback, self)
if cmd.result_callback:
cmd.result_callback = partial(cmd.result_callback, self)
except AttributeError:
pass
return cmd
Example
Putting this all together:
from functools import partial
import click
from click.testing import CliRunner
from doc_inherit import class_doc_inherit
def ensure_cli_documentation(cls):
"""
Modify a class that may contain instances of :py:class:`click.BaseCommand`
to ensure that it can be properly documented (e.g. using tools such as Sphinx).
This function will only process commands that have private callbacks i.e. are
prefixed with underscores. It will associate a new function with the class based on
this callback but without the leading underscores. This should mean that generated
documentation ignores the command instances but includes documentation for the functions
based on them.
This function should be invoked on a class when it is imported in order to do its job. This
can be done by applying it as a decorator on the class.
:param cls: the class to operate on
:return: `cls`, after performing relevant modifications
"""
for attr_name, attr_value in dict(cls.__dict__).items():
if isinstance(attr_value, click.BaseCommand) and attr_name.startswith('_'):
cmd = attr_value
try:
# noinspection PyUnresolvedReferences
new_function = cmd.callback
except AttributeError:
continue
else:
new_function_name = attr_name.lstrip('_')
assert not hasattr(cls, new_function_name)
setattr(cls, new_function_name, new_function)
return cls
#ensure_cli_documentation
#class_doc_inherit
class FooCommands(click.MultiCommand):
"""
Provides Foo commands.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._commands = [self._curry_instance_command_callbacks(self._calc)]
def list_commands(self, ctx):
return [c.name for c in self._commands]
def get_command(self, ctx, cmd_name):
try:
return next(c for c in self._commands if c.name == cmd_name)
except StopIteration:
raise click.UsageError('Undefined command: {}'.format(cmd_name))
#click.group('calc', help='mathematical calculation commands')
def _calc(self):
"""
Perform mathematical calculations.
"""
pass
#_calc.command('add', help='adds two numbers')
#click.argument('x', type=click.INT)
#click.argument('y', type=click.INT)
def _add(self, x, y):
"""
Print the sum of x and y.
:param x: the first operand
:param y: the second operand
"""
print('{} + {} = {}'.format(x, y, x + y))
#_calc.command('subtract', help='subtracts two numbers')
#click.argument('x', type=click.INT)
#click.argument('y', type=click.INT)
def _subtract(self, x, y):
"""
Print the difference of x and y.
:param x: the first operand
:param y: the second operand
"""
print('{} - {} = {}'.format(x, y, x - y))
def _curry_instance_command_callbacks(self, cmd: click.BaseCommand):
if isinstance(cmd, click.Group):
commands = [self._curry_instance_command_callbacks(c) for c in cmd.commands.values()]
cmd.commands = {}
for subcommand in commands:
cmd.add_command(subcommand)
if cmd.callback:
cmd.callback = partial(cmd.callback, self)
return cmd
#click.command(cls=FooCommands)
def cli():
pass
def main():
print('Example: Adding two numbers')
runner = CliRunner()
result = runner.invoke(cli, 'calc add 1 2'.split())
print(result.output)
print('Example: Printing usage')
result = runner.invoke(cli, 'calc add --help'.split())
print(result.output)
if __name__ == '__main__':
main()
Running main(), I get this output:
Example: Adding two numbers
1 + 2 = 3
Example: Printing usage
Usage: cli calc add [OPTIONS] X Y
adds two numbers
Options:
--help Show this message and exit.
Process finished with exit code 0
Running this through Sphinx, I can view the documentation for this in my browser:

Categories

Resources