Complete walkthrough for a new PyBuilder project¶
Installing PyBuilder¶
We’ll start by creating a folder for our new project:
mkdir myproject
cd myproject
Then, onto creating a virtualenv and install PyBuilder inside it:
virtualenv venv
source venv/bin/activate
pip install pybuilder
Scaffolding¶
Now we can use PyBuilder’s own scaffolding capabilities:
(venv) mriehl@isdeblnnl084 myproject $ pyb --start-project
Project name (default: 'myproject') :
Source directory (default: 'src/main/python') :
Docs directory (default: 'docs') :
Unittest directory (default: 'src/unittest/python') :
Scripts directory (default: 'src/main/scripts') :
Use plugin python.flake8 (Y/n)? (default: 'y') :
Use plugin python.coverage (Y/n)? (default: 'y') :
Use plugin python.distutils (Y/n)? (default: 'y') :
As you can see, this created the content roots automatically:
(venv) mriehl@isdeblnnl084 myproject $ ll --tree
inode Permissions Size Blocks User Group Date Modified Name
1488 drwxr-xr-x - - mriehl admins 28 Jul 17:46 .
1521 .rw-r--r-- 324 8 mriehl admins 28 Jul 17:46 ├── build.py
2844 drwxr-xr-x - - mriehl admins 28 Jul 17:46 ├── docs
2143 drwxr-xr-x - - mriehl admins 28 Jul 17:46 └── src
2789 drwxr-xr-x - - mriehl admins 28 Jul 17:46 ├── main
2803 drwxr-xr-x - - mriehl admins 28 Jul 17:46 │ ├── python
2864 drwxr-xr-x - - mriehl admins 28 Jul 17:46 │ └── scripts
2827 drwxr-xr-x - - mriehl admins 28 Jul 17:46 └── unittest
2829 drwxr-xr-x - - mriehl admins 28 Jul 17:46 └── python
Our new build.py
¶
Let us now take a look at the build.py
which is the centralized project description for our new project.
The annotated contents are:
from pybuilder.core import use_plugin, init
# These are the plugins we want to use in our project.
# Projects provide tasks which are blocks of logic executed by PyBuilder.
use_plugin("python.core")
# the python unittest plugin allows running python's standard library unittests
use_plugin("python.unittest")
# this plugin allows installing project dependencies with pip
use_plugin("python.install_dependencies")
# a linter plugin that runs flake8 (pyflakes + pep8) on our project sources
use_plugin("python.flake8")
# a plugin that measures unit test statement coverage
use_plugin("python.coverage")
# for packaging purposes since we'll build a tarball
use_plugin("python.distutils")
# The project name
name = "myproject"
# What PyBuilder should run when no tasks are given.
# Calling "pyb" amounts to calling "pyb publish" here.
# We could run several tasks by assigning a list to `default_task`.
default_task = "publish"
# This is an initializer, a block of logic that runs before the project is built.
@init
def set_properties(project):
# Nothing happens here yet, but notice the `project` argument which is automatically injected.
pass
Let’s run PyBuilder and see what happens:
(venv) mriehl@isdeblnnl084 myproject $ pyb
PyBuilder version 0.10.63
Build started at 2015-07-28 17:55:53
------------------------------------------------------------
[INFO] Building myproject version 1.0.dev0
[INFO] Executing build in /tmp/myproject
[INFO] Going to execute task publish
[INFO] Running unit tests
[INFO] Executing unit tests from Python modules in /tmp/myproject/src/unittest/python
[WARN] No unit tests executed.
[INFO] All unit tests passed.
[INFO] Building distribution in /tmp/myproject/target/dist/myproject-1.0.dev0
[INFO] Copying scripts to /tmp/myproject/target/dist/myproject-1.0.dev0/scripts
[INFO] Writing setup.py as /tmp/myproject/target/dist/myproject-1.0.dev0/setup.py
[INFO] Collecting coverage information
[INFO] Running unit tests
[INFO] Executing unit tests from Python modules in /tmp/myproject/src/unittest/python
[WARN] No unit tests executed.
[INFO] All unit tests passed.
[WARN] Overall coverage is below 70%: 0%
Coverage.py warning: No data was collected.
------------------------------------------------------------
BUILD FAILED - Test coverage for at least one module is below 70%
------------------------------------------------------------
Build finished at 2015-07-28 17:55:54
Build took 0 seconds (515 ms)
We don’t have any tests so our coverage is zero percent, all right! We have two ways to go about this - coverage breaks the build by default, so we can (if we want to) choose to not break the build based on the coverage metrics. This logic belongs to the project build, so we would have to add it to our build.py in the initializer. You can think of the initializer as a function that sets some configuration values before PyBuilder moves on to the actual work:
# This is an initializer, a block of logic that runs before the project is built.
@init
def set_properties(project):
project.set_property("coverage_break_build", False) # default is True
With the above modification, the coverage plugin still complains but it does not break the build. Since we’re clean coders, we’re going to add some production code with a test though!
Our first test¶
We’ll write an application that outputs “Hello world”.
Let’s start with a test at src/unittest/python/myproject_tests.py
:
from unittest import TestCase
from mock import Mock
from myproject import greet
class Test(TestCase):
def test_should_write_hello_world(self):
mock_stdout = Mock()
greet(mock_stdout)
mock_stdout.write.assert_called_with("Hello world!\n")
Note
As a default, the unittest plugin finds tests if their filename ends with _tests.py
.
We could change this with a well-placed project.set_property
of course.
Our first dependency¶
Since we’re using mock, we’ll have to install it by telling our initializer
in build.py
about it:
# This is an initializer, a block of logic that runs before the project is built.
@init
def set_properties(project):
project.set_property("coverage_break_build", False) # default is True
project.build_depends_on("mock")
We could require a specific version and so on but let’s keep it simple.
Also note that we declared mock
as a build dependency - this means it’s only required
for building and if we upload our project to PyPI then installing it from there will not require
installing mock
.
We can install our dependency by running PyBuilder with the corresponding task:
(venv) mriehl@isdeblnnl084 myproject $ pyb install_dependencies
PyBuilder version 0.10.63
Build started at 2015-07-28 19:35:37
------------------------------------------------------------
[INFO] Building myproject version 1.0.dev0
[INFO] Executing build in /tmp/myproject
[INFO] Going to execute task install_dependencies
[INFO] Installing all dependencies
[INFO] Installing build dependencies
[INFO] Installing dependency 'coverage'
[INFO] Installing dependency 'flake8'
[INFO] Installing dependency 'mock'
[INFO] Installing runtime dependencies
------------------------------------------------------------
BUILD SUCCESSFUL
------------------------------------------------------------
Build Summary
Project: myproject
Version: 1.0.dev0
Base directory: /tmp/myproject
Environments:
Tasks: install_dependencies [1480 ms]
Build finished at 2015-07-28 19:35:39
Build took 1 seconds (1486 ms)
pyb install_dependencies 1.44s user 0.10s system 98% cpu 1.570 total
Running our test¶
We can run our test now:
(venv) mriehl@isdeblnnl084 myproject $ pyb verify
PyBuilder version 0.10.63
Build started at 2015-07-28 19:36:41
------------------------------------------------------------
[INFO] Building myproject version 1.0.dev0
[INFO] Executing build in /tmp/myproject
[INFO] Going to execute task verify
[INFO] Running unit tests
[INFO] Executing unit tests from Python modules in /tmp/myproject/src/unittest/python
[ERROR] Import error in test file /tmp/myproject/src/unittest/python/myproject_tests.py, due to statement 'from myproject import greet' on line 5
[ERROR] Error importing unittest: No module named myproject
------------------------------------------------------------
BUILD FAILED - Unable to execute unit tests.
------------------------------------------------------------
Build finished at 2015-07-28 19:36:41
Build took 0 seconds (249 ms)
It’s still failing because we haven’t implemented anything yet. Let’s do that right now
in src/main/python/myproject/__init__.py
:
def greet(filelike):
filelike.write("Hello world!\n")
Any finally rerun the test:
(venv) mriehl@isdeblnnl084 myproject $ pyb verify
PyBuilder version 0.10.63
Build started at 2015-07-28 19:39:15
------------------------------------------------------------
[INFO] Building myproject version 1.0.dev0
[INFO] Executing build in /tmp/myproject
[INFO] Going to execute task verify
[INFO] Running unit tests
[INFO] Executing unit tests from Python modules in /tmp/myproject/src/unittest/python
[INFO] Executed 1 unit tests
[INFO] All unit tests passed.
[INFO] Building distribution in /tmp/myproject/target/dist/myproject-1.0.dev0
[INFO] Copying scripts to /tmp/myproject/target/dist/myproject-1.0.dev0/scripts
[INFO] Writing setup.py as /tmp/myproject/target/dist/myproject-1.0.dev0/setup.py
[INFO] Collecting coverage information
[INFO] Running unit tests
[INFO] Executing unit tests from Python modules in /tmp/myproject/src/unittest/python
[INFO] Executed 1 unit tests
[INFO] All unit tests passed.
[INFO] Overall coverage is 100%
------------------------------------------------------------
BUILD SUCCESSFUL
------------------------------------------------------------
Build Summary
Project: myproject
Version: 1.0.dev0
Base directory: /tmp/myproject
Environments:
Tasks: prepare [231 ms] compile_sources [0 ms] run_unit_tests [10 ms] package [1 ms] run_integration_tests [0 ms] verify [255 ms]
Build finished at 2015-07-28 19:39:15
Build took 0 seconds (504 ms)
Adding a script¶
Since our library is ready, we can now add a script.
We’ll just need to create src/main/scripts/greeter
:
#!/usr/bin/env python
import sys
from myproject import greet
greet(sys.stdout)
Note that there is nothing else to do. Dropping the file in src/main/scripts
is all we need to do
for PyBuilder to pick it up, because this is the convention.
Let’s look at what happens when we package it up:
(venv) mriehl@isdeblnnl084 myproject $ pyb publish
PyBuilder version 0.10.63
Build started at 2015-07-28 19:44:34
------------------------------------------------------------
[INFO] Building myproject version 1.0.dev0
[INFO] Executing build in /tmp/myproject
[INFO] Going to execute task publish
[INFO] Running unit tests
[INFO] Executing unit tests from Python modules in /tmp/myproject/src/unittest/python
[INFO] Executed 1 unit tests
[INFO] All unit tests passed.
[INFO] Building distribution in /tmp/myproject/target/dist/myproject-1.0.dev0
[INFO] Copying scripts to /tmp/myproject/target/dist/myproject-1.0.dev0/scripts
[INFO] Writing setup.py as /tmp/myproject/target/dist/myproject-1.0.dev0/setup.py
[INFO] Collecting coverage information
[INFO] Running unit tests
[INFO] Executing unit tests from Python modules in /tmp/myproject/src/unittest/python
[INFO] Executed 1 unit tests
[INFO] All unit tests passed.
[INFO] Overall coverage is 100%
[INFO] Building binary distribution in /tmp/myproject/target/dist/myproject-1.0.dev0
------------------------------------------------------------
BUILD SUCCESSFUL
------------------------------------------------------------
Build Summary
Project: myproject
Version: 1.0.dev0
Base directory: /tmp/myproject
Environments:
Tasks: prepare [227 ms] compile_sources [0 ms] run_unit_tests [9 ms] package [2 ms] run_integration_tests [0 ms] verify [252 ms] publish [241 ms]
Build finished at 2015-07-28 19:44:35
Build took 0 seconds (739 ms)
We can now simply pip install
the tarball:
(venv) mriehl@isdeblnnl084 myproject $ pip install target/dist/myproject-1.0.dev0/dist/myproject-1.0.dev0.tar.gz
Processing ./target/dist/myproject-1.0.dev0/dist/myproject-1.0.dev0.tar.gz
Building wheels for collected packages: myproject
Running setup.py bdist_wheel for myproject
Stored in directory: /data/home/mriehl/.cache/pip/wheels/89/05/9e/4b035292abf39e5d6ddcf442cc7c96c2e56f5cc49c5c673d3a
Successfully built myproject
Installing collected packages: myproject
Successfully installed myproject-1.0.dev0
(venv) mriehl@isdeblnnl084 myproject $ greeter
Hello world!
Of course since there is a setup.py
in the distribution folder, we can use
it to do whatever we want easily, for example uploading to PyPI:
(venv) mriehl@isdeblnnl084 myproject $ cd target/dist/myproject-1.0.dev0/
(venv) mriehl@isdeblnnl084 myproject-1.0.dev0 $ python setup.py upload