Create your first projects and commands

Welcome to the first tutorial. This tutorial will walk you through the creation of your first project, the different types of commands and how to use general, and project-specific configuration files.

Install Socon

If you didn’t install Socon yet, check the install section.

Creating a container

To start with Socon, we will have to take care of some initial setup. The first thing to do is to generate the layout of your Socon projects – the Socon container. To do this you can call the command createcontainer from the command line:

$ socon createcontainer tutorial

Warning

To avoid any kind of issue, do not name your container the same as any of the built-in Python or Socon components.

Note

You can add --target to specify the path where the container will be created. If the target folder already exist, it will be populated by Socon files, otherwise if the folder does not exist it will be created.

Now that we have created a container. Let’s look at what’s inside:

tutorial/
    tutorial/
        __init__.py
        settings.py
        management/
            __init__.py
            commands/
                __init__.py
    projects/
        __init__.py
    manage.py

These files are:

  • The root directory (outer tutorial/) which is the container for your projects. The name doesn’t matter, you can choose what you like.

  • manage.py: A command-line utility that lets you interact with your projects in various ways. You can read all the details about manage.py in socon and manage.py.

  • tutorial/management/commands/: A place for the common commands shared across all projects.

  • tutorial/settings.py: common settings for this Socon project. It’s the settings/configuration file for all your projects.

  • projects/: Where your projects, project-bound commands and project-configuration will live.

Creating a project

At this point you cannot really interact with Socon as you haven’t yet registered a project. To do so, we will need to create one project and register it in the global settings (/settings.py).

To create your project, be sure to be in the tutorial root directory and call:

$ python manage.py createproject artemis

Now that we have created a project, the directory structure inside artemis/ should look like this:

artemis/
    __init__.py
    management/
        __init__.py
        config.py
        commands/
            __init__.py
    projects.py

These files are:

  • The artemis/ directory is your project directory that will hold its configuration and every command, hook and manager that you will create for this project.

  • artemis/projects.py: is to configure the artemis project for example its name, the path to its configuration file and more.

  • artemis/management/config.py: is the configuration file for the artemis project. This acts the same way as tutorial/settings.py but will only be visible for this particular project. These settings can be anything ranging from the project name, description, version to database credentials etc.

  • artemis/management/commands/: The directory where all the project’s commands are stored.

Register your project

Finally you have to register your project in tutorial/settings.py, allowing Socon to see your project and all of its commands. You can find more about the settings here. Register your project by adding “projects.artemis” to INSTALLED_PROJECTS:

INSTALLED_PROJECTS = [
    "projects.artemis"
]

Registering your first command

Now that you have created your first project, you can create your first command. In Socon there are two types of commands:

  • ProjectCommand: commands that will load a ProjectConfig. And must be must be called using the --project option.

  • BaseCommand: commands can be declared anywhere and are not specific to a certain project. These commands won’t load any ProjectConfig

Note

If you want to lean more about commands and how they work, check the commands reference.

Creating the launch command

It is possible to create a command either through the command line or by manually creating a file in any commands/ folder. We will create a projectcommand called launch for the artemis project using the command line, with the use of createcommand.

$ python manage.py createcommand launch --type project --projectname artemis

Note

It is also possible to create a BaseCommand by substituting –type project with –type base.

Note

This uses the argument --projectname given to the createcommand. Using --project artemi would result in socon looking for a command named createcommand in the artemis project.

You should have something looking like this in the overall project:

tutorial/
    tutorial/
    projects/
        __init__.py
        artemis/
            management/
                __init__.py
                commands/
                    __init__.py
                    launch.py
                config.py
        ...
    ...

Check your helper

Before running your command we can quickly check that the command has been registered by running the help command:

$ python manage.py help

You should have the following output:

...

Common commands
---------------

[core]

    createcommand (G)
    createcontainer (G)
    createplugin (G)
    createproject (G)

Projects commands
-----------------

[artemis]

    launch (P)

You can see in the helper what kind of commands are registered and for which projects. You can see the core commands that you have used and the artemis project with your launch command.

Note

A (P) for project for and a (G) for general specifies the type of command. This quickly tells you if you need to specify the --project or not.

Run the command

The command can then be called using:

$ python manage.py launch --project artemis

Which should print:

Launching undefined from undefined (no info)

This command does not get any information from the configuration files yet, and therefore prints undefined. We will see how to use configurations in a later section. Let’s first analyze launch.py shortly.

Analyzing the command

Opening the launch.py shows us the following.

from argparse import ArgumentParser

from socon.conf import settings
from socon.core.management.base import ProjectCommand, Config
from socon.core.registry.base import ProjectConfig


class LaunchCommand(ProjectCommand):
    """
        Projectcommand launch.py generated
        using Socon 0.1.
    """
    name = "launch"

    def add_arguments(self, parser: ArgumentParser) -> None:
        super().add_arguments(parser)
        parser.add_argument(
            "--info", help="Add info to the handle command"
        )

    def handle(self, config: Config, project_config: ProjectConfig):
        try:
            # get common config variable
            # defined in settings.py
            config_country = settings.COUNTRY
        except AttributeError:
            config_country = "undefined"

        try:
            # get project config variable
            # defined in config.py
            projectconfig_spacecraft = project_config.get_setting("SPACECRAFT")
        except ValueError:
            projectconfig_spacecraft = "undefined"

        # get optional cli argument
        info = config.getoption("info") # cli argument

        print("Launching {:s} from {:s} {:s}".format(
            projectconfig_spacecraft,
            config_country,
            ", " + info if info else "(no info)"
        ))

LaunchCommand is a subclass of ProjectCommand. The name attribute defines the name of the command. If no name is defined Socon uses the name of the class before Command in lower case. Which mean its name would have been the same as the one we have defined above.

The handle() method must be implemented and is the starting point of the command, where you can define its behavior.

The handle() method in this template is configured to get the variable COUNTRY from the project’s configuration file (config.py) and SPACECRAFT from the common configuration file (settings.py). It also parses the command line for the optional argument --info.

Adding Configurations

Command line arguments

Imagine that you want to add an argument to your command through the command line. Socon implemented an easy way to do that using the add_arguments(). For the LaunchCommand the --info argument is already implemented.

The --info argument, shows up when calling --help on launch from the command line.

$ python manage.py launch --project artemis --help

Arguments passed to the command through the command line, such as --info, can be obtained in the handle() method through the config object by using getoption() method through config.options.xxx (e.g. config.options.info). The config object is an instance of the Config.

$ python manage.py launch --project artemis --info "in 5 days"

Should now print:

Launching undefined from undefined, in 5 days

Reading common settings

Imagine that you want to use a setting that is common to all your projects. This could for example be credentials to a shared database, the language of the project, the authors etc. For this example we are going to define a setting called "COUNTRY" as a common setting. This variable is already being read by the launch command’s handle function as shown before but can not be found at the moment.

from socon.conf import settings

from socon.core.management.base import Config
from socon.core.registry.base import ProjectConfig

def handle(self, config: Config, project_config: ProjectConfig):
    config_country = settings.COUNTRY
    ...

Each settings defined in tutorial/settings.py will be accessible to each project as an attribute of the settings object from socon.core.settings. To define this setting we will need to add the following line to tutorial/settings.py:

# Use whatever country here
COUNTRY = 'The Netherlands'

Note

You can use whatever object/ list or dictionary you want as a setting, as long as the variable name is fully in UPPERCASE.

$ python manage.py launch --project artemis --info "in 5 days"

Should now print:

Launching undefined from The Netherlands, in 5 days

Reading project settings

The projects/artemis/management/config.py is specific to the artemis project. This file is used to define project specific settings. Next we are going to define the project specific setting "SPACECRAFT". This settings is once again already being read by the launch command, but can not be found at the moment.

from socon.core.management.base import Config
from socon.core.registry.base import ProjectConfig

def handle(self, config: Config, project_config: ProjectConfig):
            ...
            projectconfig_spacecraft = project_config.get_setting("SPACECRAFT")
            ...

Each settings defined in the project configuration file (e.g projects/artemis/management/config.py) will be accessible from the project_config object and its get_setting() method. To define this setting we will need to add the following line to projects/artemis/management/config.py:

# The SpaceCraft name
SPACECRAFT = 'Orion'
$ python manage.py launch --project artemis --info "in 5 days"

Should now print:

Launching Orion from The Netherlands, in 5 days

The Common space

Creating common commands

The common space is where we will define everything common to all projects. Common commands are accessible to all project by default, and can allow interesting behaviors such as launching the same projectcommand with different configurations. This can for example be useful for deploying the same operation to different files, servers or databases.

To explore this further let’s set up some commands in the command folder. The common space directory is the tutorial folder that contains the settings.py file:

tutorial/
    tutorial/ (common space)
        management/
            commands/
                __init__.py
        __init__.py
        settings.py
    projects
    manage.py

It’s generally here that we will define basecommands and projectcommands if we want them to be available for every projects. To see how this works, let’s create a new project and add two new commands to it.

$ python manage.py createproject apollo

Warning

Don’t forget to add this new project to the installed projects in settings.py!

Consequently let’s create two new commands. This time we will create them in the common space. For this we can easily call the createcommand again, without specifying --projectname:

$ python manage.py createcommand deploy --type base
$ python manage.py createcommand build --type project

Note

Not specifying --projectname will create the commands in the common space, if createcommand is called from the root directory.

These commands are living in tutorial/tutorial/management/commands/:

tutorial/
    tutorial/
        __init__.py
        management/
            __init__.py
            commands/
                __init__.py
                deploy.py
                build.py
        settings.py
    ...

If you run manage.py python manage.py help you will see in the common section a new line with [tutorial]:

Common commands
---------------

[core]

    createcommand (G)
    createcontainer (G)
    createplugin (G)
    createproject (G)

[tutorial]

    deploy (G)
    build (P)

Projects commands
-----------------

[artemis]

    launch (P)

The common projectcommand

For this example modify deploy.py, to:

from socon.core.management.base import BaseCommand, Config


class DeployCommand(BaseCommand):
    name = "deploy"

    def handle(self, config: Config):
        print("Deploy satellites and other payloads")

And additionally modify build.py, to:

from socon.core.management.base import ProjectCommand, Config
from socon.core.registry.base import ProjectConfig


class BuildCommand(ProjectCommand):
    name = "build"

    def handle(self, config: Config, project_config: ProjectConfig):
        print("Building {:s}".project_config.get_setting('SPACECRAFT'))

Finally add the following to the apollo config file (projects/apollo/management/config.py):

# The SpaceCraft name
SPACECRAFT = 'Saturn IB'

To demonstrate that commands defined in the common space are accessible to all projects and their configurations, we will first try to start the previous launch command for the new project apollo. This command is defined in the artemis project, and should not be available for the apollo project.

$ python manage.py launch --project apollo

Doing so, you will see an error telling you that it is an unknown command and that only project artemis contains the command launch.

Now let’s do the same with the new project command build defined in the common space for the two projects.

$ python manage.py build --project apollo
$ python manage.py build --project artemis

Each commands will get you the following results:

Building Saturn IB
Building Orion

As you can see both projects have access to these commands. The build command reads the SPACECRAFT setting from the config file of the specified project.

Note

Basecommands can also be placed in the common folder but do not read a project’s configuration file, as they do not depend on any project. Therefore we also don’t have to specify the --project option when running a basecommand from the common space.

$ python manage.py deploy

Doing so the output will be:

Deploy satellites and other payloads

Congratulations! You have reached the end of the first tutorial. In the next tutorial “Change the behavior of a command” we will learn how to change the behavior of a project/general command in a specific project.