Custom commands
The command management module allows you to extend the functionality of your framework by adding commands that are common to all your projects or unique to one. Socon integrates two kind of commands:
BaseCommandthat we call general command. These commands are considered global as they don’t require to load aProjectConfig. They are essentially used to execute general tasks that are not project related.ProjectCommandinherit fromBaseCommand. These commands require a project configuration to work. For this command to work you will need to specify the--projectoption.
Register your commands
To register a command in your framework you simply need to add a management/commands
directory in one of your projects or in the common space. Socon
will register every command in each module in that directory:
myframework/
myframework/
__init__.py
management/
__init__.py
settings.py
commands/
__init__.py
launch.py
publish.py
projects/
__init__.py
apollo/
__init__.py
projects.py
management/
__init__.py
commands/
__init__.py
launch.py
config.py
artemis/
__init__.py
projects.py
manage.py
In this example, we have created multiple modules in both the common space
and in the apollo project under the management/commands directory.
In each of these modules there is a class that describe the command that we want
to register in the manage.py. Each of these commands inherit from either
the BaseCommand or the
ProjectCommand.
Project command
Let’s take a close look at the launch.py module that is defined in
the common space. The launch command defined in that module will be a
ProjectCommand command and will be made available for
any project in your framework.
Important
Because the command is declared as a ProjectCommand
and in the common space, it is available for any project that exist or that
will be created.
from socon.core.management.base import ProjectCommand, Config
from socon.core.registry.base import ProjectConfig
class LaunchCommand(ProjectCommand):
name = 'launch'
def handle(self, config: Config, project_config: ProjectConfig):
spacecraft = project_config.get_setting('SPACECRAFT')
print(f'Launching the {spacecraft} SpaceCraft to the moon')
The launch command being a ProjectCommand,
it must be called using the --project option, because
ProjectCommand must load a project configuration
to work:
If we execute this command, and if we define the SPACECRAFT
project setting as Saturn IB it will output:
Launching the Saturn IB SpaceCraft to the moon
ProjectCommand is really powerful and allows you
to make generic commands for each of your projects if it’s well defined.
Overriding project commands
Commands are based on managers and hooks. Socon registers
the built-in commands and then searches for commands in Socon,
the INSTALLED_PLUGINS, the common space, and finally the
INSTALLED_PROJECTS.
During the search, each command are registered in the CommandManager
and saved alongside it’s config object. When Socon looks for a command it will
proceed as follows:
- Search in Socon config for built-in commands.
If the command is found save it.
- Search in the plugins.
If the command is found, override the previous command.
- Search in the common space config for commands.
If the command is found, override the previous command.
- Did the user pass the
--project?Yes, if the command is found in the project, it overrides the previous command. No, return the last command found
To override a command, the new command must have the same name.
Let’s take an example to illustrate what we just said. In the above tree structure,
we have created a apollo project that defines a launch.py
module as well. Let’s take a look at what is inside:
from socon.core.management.base import ProjectCommand, Config
from myframework.management.commands.launch import LaunchCommand
class LaunchCommand(LaunchCommand):
name = 'launch'
def handle(self, config: Config, project_config: ProjectConfig):
spacecraft = project_config.get_setting('SPACECRAFT')
self.prepare(spacecraft)
super().handle(config, project_config)
def prepare(self, spacecraft):
print(f"Specific things to do for the launch of {spacecraft}")
As you can notice, the command has the same name as the one we
declared earlier. It also inherits from the one in the common space. This command
will do the exact same thing as the previous, one but it will add a new function
that will prepare the spacecraft before being launched.
We also need to specify the SPACECRAFT variable in the apollo project
config. For this example, we will define it as Orion.
If we start the command, with:
python manage.py launch --project apollo
We would have the following output:
Specific things to do for the launch of Orion
Launching the Orion SpaceCraft to the moon
Important
This overrides only the launch command of the apollo project (as it’s
the only one that redefines it). If we start the same command but with
the artemis project, we would get the result previously shown.
Management commands from plugin that have been unintentionally overridden can be made available under a new name by creating a new command in one of your projects or in the common space.
Limiting scope
You can limit the access to a ProjectCommand by using the
projects attribute. Using this attribute,
you restrict the access to this command.
from socon.core.management.base import ProjectCommand
class SimpleCommand(ProjectCommand):
name = 'simple_command'
# limit the scrope to 2 projects
projects = ['apollo', 'artemis']
def handle(...):
# ...
General commands
Let’s take a closer look at the publish.py module that is defined in
the common space. The publish command defined in that module
will be a BaseCommand command and will be made
available as a general command (as we like to call it).
..note:
This kind of command can also be declared in projects even if it does not
make a lot of sense. It's important to mention that even if you do declare
this command in a project, it will still be shown as as general command and
will not be binded to the project.
from socon.core.management.base import BaseCommand, Config
class PublishCommand(BaseCommand):
name = 'publish'
def handle(self, config: Config):
print(f'Publishing an article about NASA')
That is it, pretty simple! We use the BaseCommand
class and we give it a name. Then, this command will be available in the manage.py.
Running the command:
python manage.py publish
Would give as you all guessed:
Publishing an article about NASA
Overriding general commands
General command acts the same as project command. This means that you can override a general command in the common space, a plugin or a project.
If you create a general command in a project with the same name as one
in the common space for example, you can use it by calling your command with
the --project option. If the command exist it will be executed,
otherwise an error will be thrown as Socon expects the command to exist in the project.
There is no fallback.
You can ask yourself, why you would override a general command? Let’s take
an example where you want to redefine the built-in createproject in your
framework because you want to improve it. You just have to create the
createproject command inside the common space and it will be used instead of
the general command the next time you call it.
Accepting optional arguments
All commands can be easily modified by accepting additional command line options.
These custom options can be added in the
add_arguments() method like this:
class LaunchCommand(ProjectCommand):
name = 'launch'
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument('--countdown', help=(
"Countdown before we launch the rocket"
), default=0)
def handle(self, config: Config, project_config: ProjectConfig):
spacecraft = project_config.get_setting('SPACECRAFT')
countdown = config.getoption('countdown')
for i in range(int(countdown), 0, -1):
print(i)
sleep(1)
print(f'Launching the {spacecraft} SpaceCraft to the moon')
As you can see in this example, we have extended the functionality of our command by adding a countdown before launching our spacecraft.
We can now call this command:
python manage.py launch --countdown 60 --project apollo
The countdown option in our example is available in the config argument
of the handle method. This object, stores all the options that was passed
to the command. There are two ways to access these options:
Access to command line options using
config.options.Access the option using the
getoption()method. This method offers more possibilities than just using theconfig.optionsmethod.
In addition to being able to add custom command line options, all
management commands can accept some default options
such as --verbosity and --settings.
Abstract command
The term abstract means that the command you will define will not
be registered and available in the manage.py. It is useful when
you want to make a command as an interface for other commands.
Let’s take an example on how to make an abstract command and how it
can be used. Let’s take the publish command that we used in this document
and make it abstract.
from socon.core.management.base import BaseCommand, Config
class BasePublishCommand(BaseCommand, abstract=True):
def handle(self, config: Config):
self.create_article()
print(f'Publishing an article about NASA')
def create_article(self):
raise NotImplementedError(
"Subclass of must implement the create_article() method"
)
class PublishCommand(BasePublishCommand):
name = 'publish'
def create_article(self):
print("Are we alone in the universe?")
This example shows a BasePublishCommand that is declared as abstract.
This means that it won’t be seen in the manage.py. Only the subclassed
command will be seen.
Complementary information
Multiple commands
Multiple commands can be declared in one module. Socon will search for any
subclass of BaseCommand and
ProjectCommand that are not abstract. This will
give you the choice to organize your project as you wish.
Command name
As you might have seen, we always specified the name of a command using the
name attribute. This is not mandatory, by default
Socon will take the name of your command class in lowercase. If the name
of the class contains the word Command at the end of it like PublishCommand.
The name will be stripped out and the final name will be publish.
Keep extra args
Sometimes it is useful to pass extra arguments to another script. Socon will
allow you to do that by setting the
keep_extras_args to True.
This way you can access all the extra arguments through
extras_args.