Create your first projects and commands

Welcome to the first tutorial. This tutorial will walk you through the creation of a container that will hold all your projects. We will create one project using socon admin command and we will create commands for that specific project.

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 create a Socon container – a collection of settings that include Socon-specific options, project-specific settings, environment configuration and many others.

Before anything else, use cd to get inside a folder where you want your code to be stored. Then:

$ socon createcontainer tutorial

You can also use:

$ socon createcontainer tutorial

Using of the above commands will create a folder tutorial

Warning

To avoid any kind of issue, do not name your folder with 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 create a container. Let’s look at what’s inside:

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

These files are:

  • The outer tutorial/ root directory 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/settings.py: Settings/configuration for this Socon project. It’s the global settings file for all your projects in the container.

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.

Each project you write in Socon consists of a python package that follows a certain package convention. Socon comes with admin command that help automatically generate the basic directory structure of a project. Doing so you can focus on writing code and not creating directories.

In this tutorial, we’ll create our first project in the projects directory. To create your project, be sure to be in the tutorial container.

$ python manage.py createproject artemis

Note

For this command there is also a possibility to add --target to the commands. However this one is trickier. Socon will check that the destination is in the projects folder otherwise it will throw you an error. If you don’t use this option, you must be in a container to create the project. It doesn’t matter if this project already exists or not.

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

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

These files are:

  • The outer artemis/ root directory is your project that will hold it’s configuration and every command, hook and manager that you will create during your project lifetime.

  • artemis/projects.py: Configure your project in this file for example it’s name, the path to it’s configuration file and more.

  • artemis/management/config.py: The configuration file for your project. This acts the same way as tutorial/settings.py but will only be visible for this particular project.

Register your first command

Register your project

Now that we have created our project, we will be able to create our first command. But before that we need to register our project in the settings that you can find in tutorial/management/settings.py. You can find more about settings here.

Replace INSTALLED_PROJECTS by this one:

INSTALLED_PROJECTS = [
    'projects.artemis'
]

What we just did allows Socon to see your project and all the commands that we are going to create.

Create the launch command

First thing is to create a management/commands directory inside the artemis project:

$ cd projects/artemis
$ mkdir -p management/commands

We can then create a file in that folder that will hold our commands. The name of the file does not have any importance but for the sake of the tutorial we will use the same name as our command.

$ touch management/commands/__init__.py
$ touch management/commands/launch.py

Note

You will notice that we are using linux command to create the files/folders. Socon works perfectly on Windows so I will let you adapt the commands for your operating system.

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
        ...
    ...

Now open the launch.py command file and copy the following.

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):
        print('Launching the Orion SpaceCraft to the moon')

Let’s explain what is going on in this file. We first need to import the ProjectCommand class. This is really important. In Socon there is only two class for the commands.

  • ProjectCommand: commands that will load a ProjectConfig to access all the configuration of the project. This command, if declared inside a project will only be accessible there. If the project command is defined in the common environment (we will see later what that means) it will be accessible for every projects. This command must be called using the --project option.

  • BaseCommand: commands that is general for all projects. These commands can be declared anywhere and does not depend on any 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.

Then we need to define the subclass LaunchCommand. The name of the class is important only if you don’t want to set the name attribute. Socon will only keep the part before Command. Which mean it’s name would have been the same as the one we defined above.

Note

If for a specific reason, you want to rename your command, you can do that by overriding the name attribute.

The handle() method. This method must be implemented if you don’t want to have a nice error thrown at you. It is the starting point of the command. From here you can define any behavior for that command. We kept it simple for the example.

Check your helper

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

$ python manage.py help

You should have the following output:

...

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

[core]

    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 we used and the artemis project with our launch command.

Note

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

Run the command

Well, I guess it’s time to run the command to launch our rocket. For that it’s pretty simple:

$ python manage.py launch --project artemis

Note

Remember it’s a project command. We need to specify the project as we are using a ProjectCommand.

At this time you should see:

Launching the Orion SpaceCraft to the moon

Congratulations! You launched your first rocket ;)

Going further

Add command arguments

Imagine that you want to add an argument to your command. Well Socon implemented an easy way to do that using the add_arguments() method inside the LaunchCommand subclass.

Let’s add it.

from time import sleep

from argparse import ArgumentParser

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


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):
        countdown = config.getoption('countdown')
        for i in range(int(countdown), 0, -1):
            print(i)
            sleep(1)
        print('Launching the Orion SpaceCraft to the moon')

We are using the parser parameter to add new arguments to the command. The parser is an instance of the ArgumentParser class. It is passed to this method to extend the legacy arguments of the ProjectCommand.

If you want to see available arguments for this command:

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

The config object which is an instance of the Config class give you access to the arguments passed to the commands using the getoption() method. The config object contain also the options Namespace. This allow you to access the argument using only config.options.countdown.

Use project config and general settings

Now let’s imagine that you want to use a setting that is common to all you projects and one setting that is project specific. To do so we will need to edit two different files.

The tutorial/settings.py which is common to all projects:

# My beautifull country
COUNTRY = 'France'

Warning

You can put everything in this file but only UPPERCASE variable will be registered as global settings.

The projects/artemis/management/config.py which is specific to the artemis project:

# The SpaceCraft name
SPACECRAFT = 'Orion'

To use them it’s pretty simple:

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


class LaunchCommand(ProjectCommand):
    name = 'launch'

    def handle(self, config: Config, project_config: ProjectConfig):
        country = settings.COUNTRY
        spacecraft = project_config.get_setting('SPACECRAFT')
        print(f'Launching the {spacecraft} SpaceCraft to the moon from {country}')

Each settings defined in tutorial/settings.py will be accessible as an attribute of the settings object from socon.core.settings.

Each settings defined in the project configuration (e.g projects/artemis/management/config.py) will be accessible from the project_config object and its get_setting() method.

Now if you start the command, you should see the following:

Launching the Orion SpaceCraft to the moon from France

General command

General command are similar to project command. The major notable difference is that the handle() method does not contain the ProjectConfig object. Even if that is true, you will notice that you can still pass the --project argument to the general command because project can override general command behavior if required. We will cover that in the next tutorial: Change the behavior of a command.

A quick example on how to define a BaseCommand.

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


class PublishCommand(BaseCommand):
    name = 'publish'

    def handle(self, config: Config):
        print('Publish an article about Nasa')

Common space

The common space is where we will define everything the might or might not be common to all projects. Some might have already guessed but the common space directory is the tutorial folder that contain the settings.py file:

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

It’s generally here that we will define general and project commands 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 general settings!

Then let’s create two new commands. This time we will create them in the common space. For that you will have to create the management/commands folder with the two commands:

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

The deploy.py command file

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


class DeployCommand(BaseCommand):
    name = 'deploy'

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

The build.py command file

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(f"Building {project_config.get_setting('SPACECRAFT')}")

Now things are getting really interesting. If you get the helper from the manage.py python manage.py help you will see in the common section a new line with [tutorial]. This is where your common commands are stored. They are available for all you projects. To demonstrate that, we will try to start the previous launch command for the new project apollo:

$ python manage.py launch --project apollo

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

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

Warning

You will need to add SPACECRAFT = Saturn IB to the apollo config file. Otherwise Socon will throw you an error because the variable does not exist. You can try by yourself if you want to see what it does without the variable.

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. Now we can do the same with the general command. However, as it does not depend on any project we don’t have to specify the --project option.

$ python manage.py deploy.py

Doing do 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.