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 aboutmanage.pyin 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 theartemisproject for example its name, the path to its configuration file and more.artemis/management/config.py: is the configuration file for theartemisproject. This acts the same way astutorial/settings.pybut 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 aProjectConfig. And must be must be called using the--projectoption.BaseCommand: commands can be declared anywhere and are not specific to a certain project. These commands won’t load anyProjectConfig
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.