Pylease Documentation¶
If you are working on a fast-changing Python project which needs frequent releases, you might get in trouble by repeating some routine tasks again and again. These tasks include updating the package version, creating tags in the source repository, uploading to PyPi or to your own private repository, etc.
To escape this manual mess, Pylease comes eager to help you out by making those processes as simple as possible by requiring as little as possible.
This documentation includes all the information needed for you to use Pylease, as well as extend it further.
Introduction¶
Pylease works on Python projects that are being managed by setuptools,
i.e. have setup.py
file in their root directory. This is enough to make Pylease get things done. Pylease is an extensible modular
tool, which enables the developers to enhance it further. So let us start with the installation first.
Installation¶
Pylease is a regular Python package which comes with a command line tool, obviously called pylease
. So to start:
$ pip install pylease
And then just check if everything went fine:
$ pylease --version Pylease version 0.3
Workflow¶
The simplicity of Pylease is that it does not require any specific configuration or scripts, it accepts Python projects just as-is. Although, you can configure Pylease to fully use its features, it is not required and you can just start using Pylease on a project you were working on for years. So let us consider two scenarios.
Project from Scratch¶
So to start a new Python project, just create a new empty directory for your project and then use the init
command to create a skeleton
for your project:
$ mkdir my_project
$ cd my_project
$ pylease init my_project
Now you have the skeleton of your project ready to be used:
$ ls -LR
my_project setup.cfg setup.py
./my_project:
__init__.py
$ cat setup.py
import my_project
from setuptools import setup
setup(name='my_project',
version=my_project.__version__)
$ cat setup.cfg
[pylease]
version-files = my_project/__init__.py
$ cat my_project/__init__.py
__version__ = '0.0'
Existing Project¶
If you have an already initialised Python project, then the first thing you will want to do for feeling the presence of Pylease, is the following:
$ cd /path/to/your/project/root
$ ls
... setup.py ...
$ pylease status
Project Name: <your project>
Current Version: <project version>
Releasing a Project¶
The idea of Pylease came while doing some routine tasks during a release process of a Python project, thus the main focus of it is the
release process itself. So Pylease provides the make
command to perform an appropriate release. But before doing anything release
related, first check out your current status as it is done for an Existing Project.
So while you are working on a project, the current version defined is the last version the project was released with. As you can see in
initialising a Project from Scratch, the initial version is 0.0
, i.e. no release is done yet.
You may ask a reasonable question, why does not the current version represent the version you want to release next? The reason for that is
that it is possible that while working on the project, you might have a minor bug, small feature that will need a rapid release, so you
will need to make a patch
or minor
level release.
As already mentioned, you perform a release with the help of the make
command of Pylease. The main and required parameter of make
command is the release level, which is passed as one of the following:
--major
--minor
--patch
--dev
For instance, executing pylease make --minor
on a project with version 0.3
will update it to 0.4
:
$ pylease status
Project Name: example
Current Version: 0.3
$ pylease make --minor
$ pylease status
Project Name: example
Current Version: 0.4
So this is pretty much all what the release is. As a result, you will have your setup.py
updated to the new version. To customize the
behaviour of the release process, you might want to take a look at Pylease configuration.
Moreover, if you wish to add your own custom actions to Pylease, you should definitely get into extending Pylease.
For a quick reference, always consider to take a look at --help
messages for commands, e.g. pylease make --help
.
Built-in Plugins¶
As you will see in the Extensing Pylease section, you can customize and extend Pylease the way you like. Fortunately, Pylease comes batteries included, with built-in plugins for most common tasks. Now let us take a look at each plugin separatelly.
Git¶
Pylease supports integration with git. You can enable this plugin with the --git-tag
option of the make
command. Consider the
following situation:
$ pylease status
Project Name: example
Current Version: 0.3
$ git tag -l
v0.1
v0.1.1
v0.2
v0.3
As you can see the example project has four releases - 0.1
, 0.1.1
, 0.2
, 0.3
, and current version is 0.3
. So
imagine you want to make a patch
release and create appropriate tag in your git repository:
$ pylease make --patch --git-tag
$ pylease status
Project Name: example
Current Version: 0.3.1
$ git tag -l
v0.1
v0.1.1
v0.2
v0.3
v0.3.1
PyPI¶
The Pylease PyPI plugin enables to automatically upload your package egg to PyPI. The only prerequisite for this action is that the package name must be already registered, as well as you must be responsible for the authentication over the .pypirc file.
To enable this feature during the release process you should just use the --pypi
option like this:
$ pylease make --major --pypi
This --pypi
part of this command simply does the same as the python setup.py sdist upload
.
Pylease Configuration¶
As you have already seen, Pylease works on an existing project without requiring any configuration.
However, at some point in time you will need to make some configuration to use Pylease fully. The place for Pylease configuration is the
setup.cfg
file of your project, under the [pylease]
section. Here is an example configuration:
setup.cfg:
...
[pylease]
version-files = my_project/__init__.py
...
Following is the list of all configuration parameters with their descriptions.
version-files
A list of files where the version must be updated to the new one. Here is an example value for this parameter:
version-files = my_project/__init__.py, setup.py
use-plugins
- A list of external plugins to load. If you have installed
example_plugin
package in your Python environment, and want Pylease to use that plugin, you just need to add theexample_plugin
name to the list of this parameter.
Extending Pylease¶
Although Pylease is an Open Source project, it is impossible to consider and include all possible features at once. Moreover, one may want a particular feature urgently. Even after sending a feature request, he may have no time to wait for confirmation, implementation and release of that feature. On the other hand, Pylease may get too much loaded with features that does not get used by everyone.
For this reason, one of the major parts of Pylease is its modular extension system. It allows to develop a separate Python pakage and optionally plug it into Pylease.
So basically there are two ways to extend Pylease:
- Add before and after tasks for existing commands
- Add new commands
The following two subsections discuss both scenarios with their details.
Extending Existing Commands¶
Here is the step-by-setp guide on how to add extensions to Pylease.
The first step is inheriting the Extension
class and adding it to your package __init__.py
, where you will place
the initialisation code by implementing the load()
method. As an instance attribute you will have the
_lizy
attribute, which is an instance of Pylease
class, and contains everything you need
for your extension.
Extending an existing Pylease command is done by adding a BeforeTask
or AfterTask
(or both) to it using the Command
methods add_before_task()
and
add_after_task()
. What is needed to do is just implement those classes and add their instances to the command
that is the subject of extension. For both BeforeTask
and AfterTask
you need to
inherit and implement their execute()
method, which must include the extension logic. Also, in case of
AfterTask
, you are provided with the _command_result
attribute, which is
the result returned by the command being extended.
So basically this is the scenario of extending a Pylease command:
- Inherit and implement
BeforeTask
or/andAfterTask
- Inherit
Extension
- In the
load()
implementation get the correspondingCommand
instance from the_lizy
singleton- Add the
BeforeTask
or/andAfterTask
instances to the command instance
Adding New Commands¶
To add a new command to Pylease it is enough to implement a class by inheriting the Command
class and add it to your
package __init__.py
. Implementing the Command
class is implementing the
_process_command()
method. As an additional convenience you can inherit the NamedCommand
class instead. This will eliminate the need to manually specify the name of the command while calling the base constructor. Instead this
base class will automatically parse the command name from the class name by removing the “Command” suffix and using the rest as the
command name. So for example, the init
command is defined as a child class of NamedCommand
with the name
InitCommand
.
As in the case of implementing Extension
, here you will also be provided with the Pylease
lizy
singleton.
Rollbacks¶
Even if you ship a perfectly clear extension, which will never crash in any conditions, you have no guarantee for others. As Pylease is a modular tool, it is possible to plug any number of independent extensions. This means that it is possible that after your extension task or command is executed, there may be another task executed after, that will lead to an error. In this case, you might need to rollback all the changes that your extension made to maintain the original state of the project.
For instance, the Git plugin makes a commit for the changes of version of the project, then creates a tag for the version. If any error raises after this operations, is it critical to roll them back. Thus, this plugin deletes the last commit and removes the created tag.
Pylease provides the Rollback
class and Stage
decorator to implement this
feature in your extension. The Stage
decorator enables to have a staged rollback. For example, in case of
the Git plugin, if the error occurres in the stage of creating the version tag, the only rollback step to perform is
deleting the last commit.
For reference on using this classes please see the Class Reference for Rollback
and
Stage
Class Reference¶
-
class
pylease.
Pylease
(parser, cmd_subparsers, info_container)¶ The main class of Pylease, which contains all the needed resources for extensions. This class is initialised once and by Pylease, which is the so called
lizy
object. It is passed toCommand
andExtension
instances.-
info_container
¶ pylease.InfoContainer
Contains information about current status of the project. Minimal information is
name
andversion
.
-
commands
¶ dict
A dictionary of Pylease commands, including commands defined in extensions if any. The values of the dictionary are instances of
Command
class.
-
parser
¶ argparse.ArgumentParser
The root parser of Pylease. Use this object to add command line arguments to Pylease on the same level as
--version
and--help
.
-
config
¶ dict
A dictionary representing the configuration parsed from
setup.cfg
defined under[pylease]
section. If a configuration value in the configuration file is defined askey1 = valA, valB, valC
then the value of thekey1
key of this attribute will be an instance oflist
and be equal to['valA', 'valB, 'valC']
.
-
-
class
pylease.
InfoContainer
¶ A simple container that maps a provided dictionary to its attributes. This provides the current status of the project, and the minimal built-in information attributes are the following:
-
name
¶ str
The name of the project.
-
version
¶ str
The current versin of the project
-
is_empty
¶ bool
The status of current working directory, i.e. indicates whether it is empty or not.
-
set_info
(**kwargs)¶ Used to extend the information about the project.
Example
Below are two options on how to use/extend the
InfoContainer
:info = InfoContainer() # Option 1 info.set_info(info1='value2', info2='value2') # Option 2 more_info = {'info3': 'value3'} info.set_info(**more_info) # Then you can access your info as instance attributes print(info.info2) # will print 'value2'
-
-
class
pylease.ext.
Extension
(lizy)¶ The entry point to implementing Pylease extensions. Pylease loads subclasses of this class and invokes the
load()
method.-
_lizy
¶ pylease.Pylease
The
Pylease
singleton, that is initialised and passed to all subclass instances.
-
load
()¶ This method is being called by Pylease when all the extensions are being loaded. All the initialisation code must be implemented in the body of this method.
-
-
class
pylease.cmd.task.
BeforeTask
(rollback=None)¶ -
execute
(lizy, args)¶ The place where the extension logic goes on.
Parameters: - lizy (pylease.Pylease) – The
Pylease
singleton that provides all the needed information about the project. - args (argparse.Namespace) – The arguments supplied to the command line.
- lizy (pylease.Pylease) – The
-
-
class
pylease.cmd.task.
AfterTask
(rollback=None)¶ -
execute
(lizy, args)¶ The place where the extension logic goes on.
Parameters: - lizy (pylease.Pylease) – The
Pylease
singleton that provides all the needed information about the project. - args (argparse.Namespace) – The arguments supplied to the command line.
- lizy (pylease.Pylease) – The
-
_command_result
¶ A dictionary containing information by the completion of the command execution.
-
-
class
pylease.cmd.
Command
(lizy, name, description, rollback=None, requires_project=True)¶ This class is one of the main point of Pylease. For adding new commands just inherit from this class and implement
_process_command()
method.-
__init__
(lizy, name, description, rollback=None, requires_project=True)¶ This constructor should be called from child classes and at least be supplied with at least
name
anddescription
.Parameters: - lizy (pylease.Pylease) – The lizy object, which is initialized and passed by Pylease.
- name (str) – The name of the command, which will appear in the usage output.
- description (str) – Description of the command which will also appear in the help message.
- rollback (pylease.cmd.rollback.Rollback) – The rollback object that will be executed in case of failure during or after the
command. This parameter may be emitted if the command does not need a rollback, or may be set in the process of command
execution using the
enable_rollback()
method, if it depends on some parameters during runtime. - requires_project (bool) – Boolean indicating whether the command requires to operate on an existing project. E.g. the
init
command requires an empty directory.
-
_process_command
(lizy, args)¶ The method which should be implemented when inheriting the
Command
. All the command logic must go into this method.Parameters: - lizy (pylease.Pylease) – The
Pylease
singleton. - args (argparse.Namespace) – The arguments passed to the command line while invoking Pylease.
- lizy (pylease.Pylease) – The
-
add_before_task
(task)¶ Adds a
BeforeTask
to theCommand
.Parameters: task (pylease.cmd.task.BeforeTask) – The task to be added.
-
add_after_task
(task)¶ Adds a
AfterTask
to theCommand
.Parameters: task (pylease.cmd.task.AfterTask) – The task to be added.
-
-
class
pylease.cmd.
NamedCommand
(lizy, description, rollback=None, requires_project=True)¶ Same as the
Command
class, however this class enables a little taste of convenience. You can define a class having name with a suffix “Command” and it will automatically assign the prefix of the class name as the command name.-
__init__
(lizy, description, rollback=None, requires_project=True)¶ Same as
__init__()
ofCommand
class, except that thename
argument is passed automatically.
-
-
class
pylease.cmd.rollback.
Rollback
¶ This class provides a facility to define a staged rollback process. The scenario of using this class is the following:
- Inherit
Rollback
class - Define rollback stages as instance methods
- Decorate each rollback method with
Stage
decorator, by specifying stage name and priority - Enable each stage separately calling
enable_stage()
method
-
enable_stage
(stage)¶ Enable particular stage by name.
Parameters: stage (str) – Stage name to enable.
-
rollback
()¶ Execute all rollback stages ordered by priority.
- Inherit
-
class
pylease.cmd.rollback.
Stage
(stage, priority=0)¶ Decorator used in custom
Rollback
classes for associating each method with a stage, and setting priority.Parameters: - stage (str) – The name of the stage.
- priority (int) – The order priority of the stage to be rolled back. Defaults to
0
.
Example
Here is an example of how to use the
Stage
decorator in combination with theRollback
base class:class ExampleRollback(Rollback): @Stage('some_stage', 1) def some_stage_with_priority_1(self): pass # your some_stage rollback goes here