Repository: ESSS/jobs_done10 (language: python)

Issue:
Add action to run test (PR and cron)



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..1b8699c
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,55 @@
+# vim: set ft=yaml ts=2 sw=2:
+
+name: tests
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+jobs:
+  test:
+    runs-on: 'ubuntu-latest'
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Run tests
+        run: |
+          touch .env
+          docker build . -t jobsdone --build-arg SETUPTOOLS_SCM_PRETEND_VERSION=0.1
+          docker run jobsdone pytest src --color=yes
+
+
+  deploy:
+    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'ESSS/jobs_done10'
+
+    runs-on: 'ubuntu-latest'
+    needs: [ test ]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+          persist-credentials: false
+
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: "3.7"
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install --upgrade build
+
+      - name: Build package
+        run: |
+          python -m build
+
+      - name: Publish package to PyPI
+        uses: pypa/gh-action-pypi-publish@master
+        with:
+          user: __token__
+          password: ${{ secrets.pypi_token }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..16cafe8
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,23 @@
+repos:
+-   repo: https://github.com/psf/black
+    rev: 21.10b0
+    hooks:
+    -   id: black
+        args: [--safe, --quiet]
+        language_version: python3
+-   repo: https://github.com/asottile/blacken-docs
+    rev: v1.11.0
+    hooks:
+    -   id: blacken-docs
+        additional_dependencies: [black==20.8b1]
+-   repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.0.1
+    hooks:
+    -   id: trailing-whitespace
+    -   id: end-of-file-fixer
+    -   id: debug-statements
+-   repo: https://github.com/asottile/reorder_python_imports
+    rev: v2.6.0
+    hooks:
+    -   id: reorder-python-imports
+        args: ['--application-directories=.:src', --py36-plus]
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 4400b58..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-language: python
-stages:
-- test
-- name: deploy
-  if: repo = ESSS/jobs_done10 AND tag IS present
-jobs:
-  include:
-    - python: "3.6"
-      install:
-      - "touch .env"
-      - "docker build . -t jobsdone --build-arg SETUPTOOLS_SCM_PRETEND_VERSION=0.1"
-      script:
-      - "docker run jobsdone pytest src --color=yes"
-
-    - stage: "deploy"
-      python: "3.6"
-      install: pip install -U setuptools setuptools_scm
-      script: skip
-
-      deploy:
-        provider: pypi
-        user: nicoddemus
-        distributions: sdist bdist_wheel
-        skip_upload_docs: true
-        password:
-          secure: Ga4g/cNolGq5dJvJB5Bs0KUf2ZIANO3xGR3AtL3saiycF+NQyyBlTJoPI/YFc5r9QnOGPYpERbiZ9c5wF5/FA+UGo7hgcQrPta6g+nzxJ0VjnaUTatfQRGIBRC68wtN/As0gF2c+moV+LrdUDS2/6kPZXSF+igF29qUDLnJOkNSrM3uWqbnRT2zZw1hT5TDU+6WYIK3BjXBCbh71IZIvBd7QgfhygvisISyXFCAN3OKGs8QNXVnncuN7oZ9G+A2myH1tSZmRG4mM/xb96Tovm4uw8jMDsvJEUCV757ggdJfTlh387cI82bmHR5DzKb2HyxftRpGteJQvI/9EI41/oK03REwffxxAZBd6Wv/GHk2jNBvnI0bFn5yqROkurECmNAwx9X6h/4fOFHcX87d43nrK3avnHrM6A/z37x/NRJeqBz12CwrpnxN7ERVRKo9OwtjKJJ/j3+wDmKe/cj2YZnNXs94g2OlQg8pgKliWzmsYmSqkneTe0vF1Dm00Ioc08is9dDsCmxvSJO2Z3VjltpwscH7veHJ9sNBk09zdOFCkw0U3NXrURlSenkDNpGv/YjZ7fT29ZgpoxsOG4GTl5Dzx96jfBqkCfxDaRzq3GIToPzYEahTYQr+BDtUTMwEGgfykF+DMPqrRUeNHQfeFdZsyW3ptnmAgrIf76jGLZ5Q=
-        on:
-          tags: true
-          repo: ESSS/jobs_done10
-
diff --git a/README.md b/README.md
index 9fd16ab..3d7604a 100644
--- a/README.md
+++ b/README.md
@@ -3,12 +3,12 @@ Job's Done
 
 [![link](https://img.shields.io/pypi/v/jobs_done10.svg)](https://pypi.org/project/jobs_done10)
 [![link](https://img.shields.io/pypi/pyversions/jobs_done10.svg)](https://pypi.org/project/jobs_done10)
-[![link](https://travis-ci.com/ESSS/jobs_done10.svg?branch=master)](https://travis-ci.com/ESSS/jobs_done10)
+[![link](https://github.com/pytest-dev/pytest/workflows/ci/badge.svg)](https://github.com/ESSS/jobs_done10/actions)
 [![link](https://img.shields.io/github/license/ESSS/jobs_done10.svg)](https://img.shields.io/github/license/ESSS/jobs_done10.svg)
 
 # About #
 
-Job's Done is a tool heavily inspired by [Travis](https://travis-ci.org/), and works in the same way 
+Job's Done is a tool heavily inspired by [Travis](https://travis-ci.org/), and works in the same way
 in that configuring a `.jobs_done.yaml` file in your repository's root to create and trigger Continuous Integration jobs.
 
 Example of a `.jobs_done.yaml` file:
@@ -20,17 +20,17 @@ matrix:
   - "redhat64"
 
 platform-win64:build_batch_commands:
-- | 
+- |
   python -m venv .env3   || goto eof
   call .env3\Scripts\activate   || goto eof
-  pytest --junitxml=tests-{platform}.xml  
- 
+  pytest --junitxml=tests-{platform}.xml
+
 platform-redhat64:build_shell_commands:
 - |
   python3 -m venv .env3
   source .env3/bin/activate
-  pytest --junitxml=tests-{platform}.xml  
- 
+  pytest --junitxml=tests-{platform}.xml
+
 junit_patterns:
 - "tests.*.xml"
 ```
@@ -43,12 +43,12 @@ Considering this file is in the root of repository `myproject` and was pushed to
 
 ## Command-line ###
 
-Jobs done can be executed in the command-line. 
+Jobs done can be executed in the command-line.
 
 To use it, from the repository's folder that you want to create jobs for, execute:
 
 ```console
-$ jobs_done jenkins --username USER https://example.com/jenkins 
+$ jobs_done jenkins --username USER https://example.com/jenkins
 ```
 
 This will create/update existing jobs.
@@ -97,7 +97,7 @@ Below are the possible installation options.
 
 ## Server ##
 
-jobs done includes a `flask` end point in `jobs_done10.server` which can be deployed using [Docker](https://www.docker.com/). 
+jobs done includes a `flask` end point in `jobs_done10.server` which can be deployed using [Docker](https://www.docker.com/).
 
 This end point is tailored to receive the push event from a Webhook of a BitBucket Server instance. A post without any
 json data will return the installed version, useful to check the installed version and that the end point is correct.
@@ -121,7 +121,7 @@ JD_EMAIL_FROM=JobsDone Bot <mail-sender@example.com>
 JD_EMAIL_PASSWORD=email password
 JD_EMAIL_SERVER=smtp.example.com
 JD_EMAIL_PORT=587
-``` 
+```
 
 ### Build ###
 
@@ -130,8 +130,8 @@ Clone the repository and checkout the tag:
 ```console
 $ git clone https://github.com/ESSS/jobs_done10.git
 $ cd jobs_done10
-$ git checkout <VERSION> 
-``` 
+$ git checkout <VERSION>
+```
 
 Build a docker image:
 
@@ -156,7 +156,7 @@ build_batch_commands:
 description_regex: "MESSAGE\\:(.*)"
 ```
 
-Adding this file to a repository hooked into our CI system will create a single job that when executed run a 
+Adding this file to a repository hooked into our CI system will create a single job that when executed run a
 Windows batch command, and later on catches the message echoed and sets that as the build description.
 
 
@@ -173,7 +173,7 @@ junit_patterns:
 - "pytest_results.xml"
 ```
 
-This jobs runs pytest in the repository and outputs test results to a file. We also configure the job to look for that 
+This jobs runs pytest in the repository and outputs test results to a file. We also configure the job to look for that
 file, and present test results to us at then end of the build.
 
 # Multiple platforms #
@@ -185,35 +185,35 @@ The same application as above, but now running on multiple platforms.
 
 platform-win64:build_batch_commands:
 - "pytest --junitxml=pytest_results-{platform}.xml"
- 
+
 platform-redhat64:build_shell_commands:
 - "pytest --junitxml=pytest_results-{platform}.xml"
- 
+
 junit_patterns:
 - "pytest_results.*.xml"
- 
+
 matrix:
   platform:
   - "win64"
   - "redhat64"
 ```
 
-Here we add a **matrix** section to define variations of this job. In this case, we have the platform variable, 
+Here we add a **matrix** section to define variations of this job. In this case, we have the platform variable,
 with two possible values, `win64` and `redhat64`.
 
 One job will be created for each possible combination in the matrix (only two jobs in this case).
 
-Since we can't run batch commands in linux, we add another builder section, `build_shell_commands`. Using some flags 
+Since we can't run batch commands in linux, we add another builder section, `build_shell_commands`. Using some flags
 before defining sections we can choose which one will be available in each job.
 
-Values from the matrix can also be used as variables, in this case, `{platform}` will be replaced by the platform used 
+Values from the matrix can also be used as variables, in this case, `{platform}` will be replaced by the platform used
 in that job (`win64` or `redhat64`).
 
 
 ## Branch patterns ##
 
-Branch patterns are used to filter which branches will produce jobs. This list of regular expressions 
-(using Python syntax), ensures that a branch will only produce jobs if at least one of the regular 
+Branch patterns are used to filter which branches will produce jobs. This list of regular expressions
+(using Python syntax), ensures that a branch will only produce jobs if at least one of the regular
 expressions matches the name of the branch.
 
 Here's an example that filter only `master` and feature branches:
@@ -229,7 +229,7 @@ If this section is not defined in the file, all branches will produce jobs.
 
 ## Job Matrix ##
 
-As shown in the examples above, the job matrix can be used to create multiple variations of a job. 
+As shown in the examples above, the job matrix can be used to create multiple variations of a job.
 One job for each combination of entries in this matrix is created.
 
 ```yaml
@@ -262,8 +262,8 @@ matrix:
   platform:
   - "win64"
   - "linux64"
-  
-mode-cases:platform-win.*:exclude: "yes"  
+
+mode-cases:platform-win.*:exclude: "yes"
 ```
 
 This will exclude all cases jobs from windows.
@@ -283,9 +283,9 @@ matrix:
   platform:
   - "win64"
   - "linux64"
-  
+
 platform-win.*:build_batch_commands:
-- "echo Building project {name} in branch {branch} on platform {platform}"  
+- "echo Building project {name} in branch {branch} on platform {platform}"
 ```
 
 Note that we use Python's format syntax, so if you need an actual `{` or `}` use double braces: `{{`, `}}`.
@@ -293,7 +293,7 @@ Note that we use Python's format syntax, so if you need an actual `{` or `}` use
 
 ## Condition Flags ##
 
-Variables defined in your job matrix can also be used to control some of the contents in your job file. 
+Variables defined in your job matrix can also be used to control some of the contents in your job file.
 A common example here is using different builder for windows (bash) and linux (shell).
 
 This is done by adding a prefix to sections of the YAML file, with the variable name and value necessary to use it:
@@ -301,7 +301,7 @@ This is done by adding a prefix to sections of the YAML file, with the variable
 ```yaml
 platform-win.*:build_batch_commands:
 - "dir ."
- 
+
 platform-linux.*:build_shell_commands:
 - "ls -la ."
 
@@ -311,13 +311,13 @@ matrix:
   - "linux64"
 ```
 
-Matrix variables can also define aliases, useful to reduce duplication when using such flags. 
+Matrix variables can also define aliases, useful to reduce duplication when using such flags.
 To add aliases, simply use commas to separate additional names for matrix values:
 
 ```yaml
 platform-windows:build_batch_commands:
 - "dir ."
- 
+
 platform-linux:build_shell_commands:
 - "ls -la ."
 
@@ -333,7 +333,7 @@ On top of that you can use a special variable `branch` that's always available,
 ```yaml
 branch-master:build_batch_commands:
 - "echo Build"
- 
+
 branch-deploy:build_batch_commands:
 - "echo Build + Deploy"
 ```
@@ -343,10 +343,10 @@ Condition values can use Python regex syntax for extra flexibility:
 ```yaml
 branch-master:build_batch_commands:
 - "echo Build"
- 
+
 branch-fb.*:build_batch_commands:
 - "echo Feature branch!"
- 
+
 branch-rb.*:build_batch_commands:
 - "echo Release branch!"
 ```
@@ -461,7 +461,7 @@ Enable support for ANSI escape sequences, including color, to Console Output.
 
 Requires [AnsiColor Plugin](https://wiki.jenkins-ci.org/display/JENKINS/AnsiColor+Plugin).
 
-Accepted values: 
+Accepted values:
 * `xterm` (default)
 * `vga`
 * `css`
@@ -481,7 +481,7 @@ Require [Cobertura Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Cobertura
 Options:
 
 * `report_pattern`: mandatory, pattern where XML coverage files are searched. These XML files are usually in
-  [Cobertura](http://cobertura.github.io/cobertura) format, which is also format by 
+  [Cobertura](http://cobertura.github.io/cobertura) format, which is also format by
   [pytest-cov](https://pypi.python.org/pypi/pytest-cov) XML output (because pytest-cov uses coverage library).
 * `healthy`: optional, specifies desired method, line and conditional metric. Any omitted metric defaults to `80`.
 * `unhealthy`: optional, specifies desired method, line and conditional metric. Any omitted metric defaults to `0`. Builds below these thresholds are marked as unhealthy.
@@ -551,7 +551,7 @@ email_notification: "email1@example.com email2@example.com"
 # or
 
 email_notification:
-  recipients: "email1@example.com email2@example.com" 
+  recipients: "email1@example.com email2@example.com"
   notify_every_build: true
   notify_individuals: true
 ```
@@ -573,7 +573,7 @@ Requires [Git Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin).
 Options available here are shared with `additional_repositories`.
 
 ```yaml
-git:  
+git:
   target_dir: ""
   recursive_submodules: "true"
   reference: "/path/to/ref_repos"
@@ -635,9 +635,9 @@ notify_stash:
   url: "example.com/stash"
   username: "user"
   password: "pass"
-  
+
 # Using default from Jenkins
-notify_stash:  
+notify_stash:
 ```
 
 ### parameters ###
@@ -696,7 +696,7 @@ timeout: 60
 
 ### timestamps ###
 
-Show timestamps on the left side of the console output. 
+Show timestamps on the left side of the console output.
 
 Requires the [Timestamper Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Timestamper).
 
diff --git a/setup.py b/setup.py
index 56060bc..944c1c6 100644
--- a/setup.py
+++ b/setup.py
@@ -1,67 +1,61 @@
-
-from setuptools import find_packages, setup
+from setuptools import find_packages
+from setuptools import setup
 
 
 with open("README.md") as fd:
     long_description = fd.read()
 
 setup(
-    name='jobs_done10',
-    provides=['jobs_done10'],
-
+    name="jobs_done10",
+    provides=["jobs_done10"],
     use_scm_version={"write_to": "src/jobs_done10/_version.py"},
-    setup_requires=['setuptools_scm'],
-
-    #===============================================================================================
+    setup_requires=["setuptools_scm"],
+    # ===============================================================================================
     # Requirements
-    #===============================================================================================
+    # ===============================================================================================
     install_requires=[
-        'click',
-        'flask',
-        'mailer',
-        'pygments',
-        'python-dotenv',
-        'python-jenkins',
-        'pyyaml',
-        'requests-mock',
+        "click",
+        "flask",
+        "mailer",
+        "pygments",
+        "python-dotenv",
+        "python-jenkins",
+        "pyyaml",
+        "requests-mock",
     ],
     extras_require={
-        'testing': [
-            'pytest',
-            'pytest-mock',
+        "testing": [
+            "pre-commit",
+            "pytest",
+            "pytest-mock",
         ],
     },
-
-    #===============================================================================================
+    # ===============================================================================================
     # Packaging
-    #===============================================================================================
-    entry_points={'console_scripts': ['jobs_done=jobs_done10.cli:jobs_done']},
-    packages=find_packages('src'),
+    # ===============================================================================================
+    entry_points={"console_scripts": ["jobs_done=jobs_done10.cli:jobs_done"]},
+    packages=find_packages("src"),
     package_dir={
-        '' : 'src',
+        "": "src",
     },
     include_package_data=True,
-
-    #===============================================================================================
+    # ===============================================================================================
     # Project description
-    #===============================================================================================
-    author='ESSS',
-    author_email='dev@esss.com.br',
-
-    url='https://github.com/ESSS/jobs_done10',
-
-    license='MIT',
-    description=\
-        "Job's Done uses a .jobs_done.yaml file in your repository to create Continuous Integration jobs on Jenkins.",
+    # ===============================================================================================
+    author="ESSS",
+    author_email="dev@esss.com.br",
+    url="https://github.com/ESSS/jobs_done10",
+    license="MIT",
+    description="Job's Done uses a .jobs_done.yaml file in your repository to create Continuous Integration jobs on Jenkins.",
     long_description=long_description,
     long_description_content_type="text/markdown",
-    keywords='jenkins continuous integration ci jobs job build',
+    keywords="jenkins continuous integration ci jobs job build",
     classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: MIT License',
-        'Operating System :: OS Independent',
-        'Programming Language :: Python :: 3.6',
-        'Topic :: Software Development',
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: MIT License",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python :: 3.6",
+        "Topic :: Software Development",
     ],
 )
diff --git a/source.list b/source.list
index a661180..3717750 100644
--- a/source.list
+++ b/source.list
@@ -5,4 +5,4 @@ deb http://deb.debian.org/debian-security/ buster/updates main
 deb-src http://deb.debian.org/debian-security/ buster/updates main
 
 deb http://deb.debian.org/debian buster-updates main
-deb-src http://deb.debian.org/debian buster-updates main
\ No newline at end of file
+deb-src http://deb.debian.org/debian buster-updates main
diff --git a/src/jobs_done10/__init__.py b/src/jobs_done10/__init__.py
index 72dbc24..965ec82 100644
--- a/src/jobs_done10/__init__.py
+++ b/src/jobs_done10/__init__.py
@@ -1,9 +1,8 @@
-
 def get_version_title():
     import pkg_resources
 
     try:
-        version = pkg_resources.get_distribution('jobs_done10').version
+        version = pkg_resources.get_distribution("jobs_done10").version
     except pkg_resources.DistributionNotFound:
-        version = '<N/A>'
-    return f'jobs_done10 ver. {version}'
+        version = "<N/A>"
+    return f"jobs_done10 ver. {version}"
diff --git a/src/jobs_done10/__main__.py b/src/jobs_done10/__main__.py
index f468ee9..9067c29 100644
--- a/src/jobs_done10/__main__.py
+++ b/src/jobs_done10/__main__.py
@@ -1,5 +1,3 @@
-
 from .cli import jobs_done
 
-jobs_done(prog_name='jobs_done')
-
+jobs_done(prog_name="jobs_done")
diff --git a/src/jobs_done10/cli.py b/src/jobs_done10/cli.py
index fc8406d..5a59b53 100644
--- a/src/jobs_done10/cli.py
+++ b/src/jobs_done10/cli.py
@@ -1,11 +1,10 @@
-
 import click
 
 
 @click.command()
-@click.argument('url')
-@click.option('--username', prompt=True, help='Jenkins username.')
-@click.option('--password', prompt=True, hide_input=True, help='Jenkins password.')
+@click.argument("url")
+@click.option("--username", prompt=True, help="Jenkins username.")
+@click.option("--password", prompt=True, hide_input=True, help="Jenkins password.")
 def jenkins(url, username=None, password=None):
     """
     Push jobs to Jenkins instance.
@@ -14,29 +13,31 @@ def jenkins(url, username=None, password=None):
     """
     from jobs_done10.generators.jenkins import GetJobsFromDirectory, JenkinsJobPublisher
 
-    click.secho('Publishing jobs in ', nl=False)
-    click.secho(url, fg='white')
+    click.secho("Publishing jobs in ", nl=False)
+    click.secho(url, fg="white")
 
     repository, jobs = GetJobsFromDirectory()
     publisher = JenkinsJobPublisher(repository, jobs)
-    new_jobs, updated_jobs, deleted_jobs = publisher.PublishToUrl(url, username, password)
+    new_jobs, updated_jobs, deleted_jobs = publisher.PublishToUrl(
+        url, username, password
+    )
 
     for job in new_jobs:
-        click.secho('NEW', fg='green', nl=False)
-        click.secho(' - ', nl=False)
+        click.secho("NEW", fg="green", nl=False)
+        click.secho(" - ", nl=False)
         click.secho(job)
     for job in updated_jobs:
-        click.secho('UPD', fg='yellow', nl=False)
-        click.secho(' - ', nl=False)
+        click.secho("UPD", fg="yellow", nl=False)
+        click.secho(" - ", nl=False)
         click.secho(job)
     for job in deleted_jobs:
-        click.secho('DEL', fg='red', nl=False)
-        click.secho(' - ', nl=False)
+        click.secho("DEL", fg="red", nl=False)
+        click.secho(" - ", nl=False)
         click.secho(job)
 
 
 @click.command()
-@click.argument('output_directory')
+@click.argument("output_directory")
 def jenkins_test(output_directory):
     """
     Save the resulting '.xml's in a directory.
@@ -49,16 +50,16 @@ def jenkins_test(output_directory):
     publisher = JenkinsJobPublisher(repository, jobs)
     publisher.PublishToDirectory(output_directory)
 
-    click.secho('OK', fg='green')
+    click.secho("OK", fg="green")
 
 
 try:
     from ._version import version
 except ImportError:
-    version = 'DEV'
+    version = "DEV"
 
 
-@click.group(name='jobs_done10')
+@click.group(name="jobs_done10")
 @click.version_option(version=version)
 def jobs_done():
     """
@@ -69,4 +70,3 @@ def jobs_done():
 
 jobs_done.add_command(jenkins)
 jobs_done.add_command(jenkins_test)
-
diff --git a/src/jobs_done10/common.py b/src/jobs_done10/common.py
index aea38ff..b068a00 100644
--- a/src/jobs_done10/common.py
+++ b/src/jobs_done10/common.py
@@ -1,7 +1,4 @@
-
-
-
 def AsList(arg):
     """return the given argument unchanged if already a list or tuple, otherwise return a single
     element list"""
-    return arg if isinstance(arg, (tuple, list)) else [arg]
\ No newline at end of file
+    return arg if isinstance(arg, (tuple, list)) else [arg]
diff --git a/src/jobs_done10/generators/__init__.py b/src/jobs_done10/generators/__init__.py
index 8b13789..e69de29 100644
--- a/src/jobs_done10/generators/__init__.py
+++ b/src/jobs_done10/generators/__init__.py
@@ -1,1 +0,0 @@
-
diff --git a/src/jobs_done10/generators/jenkins.py b/src/jobs_done10/generators/jenkins.py
index 45a2aca..2099967 100644
--- a/src/jobs_done10/generators/jenkins.py
+++ b/src/jobs_done10/generators/jenkins.py
@@ -1,18 +1,16 @@
-'''
+"""
 Module containing everything related to Jenkins in jobs_done10.
 
 This includes a generator, job publishers, constants and command line interface commands.
-'''
-
-
+"""
 import io
 from collections import namedtuple
 
 from jobs_done10.common import AsList
 
-#===================================================================================================
+# ===================================================================================================
 # JenkinsJob
-#===================================================================================================
+# ===================================================================================================
 #
 # Represents a Jenkins job.
 #
@@ -24,17 +22,16 @@
 #
 # :cvar unicode xml:
 #     Job XML contents
-JenkinsJob = namedtuple('JenkinsJob', 'name repository xml')
-
+JenkinsJob = namedtuple("JenkinsJob", "name repository xml")
 
 
-#===================================================================================================
+# ===================================================================================================
 # JenkinsXmlJobGenerator
-#===================================================================================================
+# ===================================================================================================
 class JenkinsXmlJobGenerator(object):
-    '''
+    """
     Generates Jenkins jobs.
-    '''
+    """
 
     def __init__(self):
         # Initialize some variables
@@ -43,38 +40,38 @@ def __init__(self):
 
         self.repository = None
 
-
     def Reset(self):
         from jobs_done10.xml_factory import XmlFactory
 
-        self.xml = XmlFactory('project')
-        self.xml['description'] = "<!-- Managed by Job's Done -->"
-        self.xml['keepDependencies'] = xmls(False)
-        self.xml['logRotator/daysToKeep'] = 7
-        self.xml['logRotator/numToKeep'] = -1
-        self.xml['logRotator/artifactDaysToKeep'] = -1
-        self.xml['logRotator/artifactNumToKeep'] = -1
-        self.xml['blockBuildWhenDownstreamBuilding'] = xmls(False)
-        self.xml['blockBuildWhenUpstreamBuilding'] = xmls(False)
-        self.xml['concurrentBuild'] = xmls(False)
-        self.xml['canRoam'] = xmls(False)
+        self.xml = XmlFactory("project")
+        self.xml["description"] = "<!-- Managed by Job's Done -->"
+        self.xml["keepDependencies"] = xmls(False)
+        self.xml["logRotator/daysToKeep"] = 7
+        self.xml["logRotator/numToKeep"] = -1
+        self.xml["logRotator/artifactDaysToKeep"] = -1
+        self.xml["logRotator/artifactNumToKeep"] = -1
+        self.xml["blockBuildWhenDownstreamBuilding"] = xmls(False)
+        self.xml["blockBuildWhenUpstreamBuilding"] = xmls(False)
+        self.xml["concurrentBuild"] = xmls(False)
+        self.xml["canRoam"] = xmls(False)
 
         # Configure git SCM
-        self.git = self.xml['scm']
-        self.git['@class'] = 'hudson.plugins.git.GitSCM'
-
-        self.SetGit(dict(
-            url=self.repository.url,
-            target_dir=self.repository.name,
-            branch=self.repository.branch
-        ))
+        self.git = self.xml["scm"]
+        self.git["@class"] = "hudson.plugins.git.GitSCM"
+
+        self.SetGit(
+            dict(
+                url=self.repository.url,
+                target_dir=self.repository.name,
+                branch=self.repository.branch,
+            )
+        )
 
         self.job_name = None
 
-
     @classmethod
     def GetJobGroup(cls, repository):
-        '''
+        """
         A single repository/branch combination can create many jobs, depending on the job matrix.
 
         This variable is used to identify jobs within the same group.
@@ -84,20 +81,19 @@ def GetJobGroup(cls, repository):
 
         :returns unicode:
             Job group for the given repository
-        '''
-        return repository.name + '-' + repository.branch
-
+        """
+        return repository.name + "-" + repository.branch
 
     def GetJob(self):
-        '''
+        """
         :return JenkinsJob:
             Job created by this generator.
-        '''
+        """
         # Mailer must always be at the end of the XML contents, otherwise Jenkins might try to
         # send emails before checking if tests passed.
-        publishers = self.xml.root.find('publishers')
+        publishers = self.xml.root.find("publishers")
         if publishers is not None:
-            mailer = publishers.find('hudson.tasks.Mailer')
+            mailer = publishers.find("hudson.tasks.Mailer")
             if mailer is not None:
                 publishers.remove(mailer)
                 publishers.append(mailer)
@@ -105,67 +101,62 @@ def GetJob(self):
         return JenkinsJob(
             name=self.job_name,
             repository=self.repository,
-            xml=self.xml.GetContents(xml_header=True)
+            xml=self.xml.GetContents(xml_header=True),
         )
 
-
-
-    #===============================================================================================
+    # ===============================================================================================
     # Configurator functions (.. seealso:: JobsDoneJob ivars for docs)
-    #===============================================================================================
+    # ===============================================================================================
     def SetRepository(self, repository):
         self.repository = repository
 
-
     def SetMatrix(self, matrix, matrix_row):
         label_expression = self.repository.name
         self.job_name = self.GetJobGroup(self.repository)
 
         if matrix_row:
-            row_representation = '-'.join([
-                value for key, value \
-                in sorted(matrix_row.items()) \
-
-                # Matrix rows with only one possible value do not affect the representation
-                if len(matrix[key]) > 1
-            ])
+            row_representation = "-".join(
+                [
+                    value
+                    for key, value in sorted(matrix_row.items())
+                    # Matrix rows with only one possible value do not affect the representation
+                    if len(matrix[key]) > 1
+                ]
+            )
 
             if row_representation:  # Might be empty
-                label_expression += '-' + row_representation
-                self.job_name += '-' + row_representation
+                label_expression += "-" + row_representation
+                self.job_name += "-" + row_representation
 
         self.SetLabelExpression(label_expression)
 
-
     def SetAdditionalRepositories(self, repositories):
         # Remove current git configuration from xml
         self.xml.root.remove(self.git.root)
 
         # Create a MultiSCM block
-        multi_scm = self.xml['scm']
-        multi_scm['@class'] = 'org.jenkinsci.plugins.multiplescms.MultiSCM'
+        multi_scm = self.xml["scm"]
+        multi_scm["@class"] = "org.jenkinsci.plugins.multiplescms.MultiSCM"
 
         # Add the current git implementation to multi_scm
         self.git.root.attrib = {}
-        self.git.root.tag = 'hudson.plugins.git.GitSCM'
-        multi_scm['scms'].root.append(self.git.root)
+        self.git.root.tag = "hudson.plugins.git.GitSCM"
+        multi_scm["scms"].root.append(self.git.root)
 
         # Replace main git with the one inside multi_scm
-        self.git = multi_scm['scms/hudson.plugins.git.GitSCM']
+        self.git = multi_scm["scms/hudson.plugins.git.GitSCM"]
 
         # Add additional repositories
         for repo in repositories:
-            self.SetGit(repo['git'], git_xml=multi_scm['scms/hudson.plugins.git.GitSCM+'])
-
-
+            self.SetGit(
+                repo["git"], git_xml=multi_scm["scms/hudson.plugins.git.GitSCM+"]
+            )
 
     def SetAuthToken(self, auth_token):
-        self.xml['authToken'] = auth_token
-
+        self.xml["authToken"] = auth_token
 
     def SetBoosttestPatterns(self, boosttest_patterns):
-        self._SetXunit('BoostTestJunitHudsonTestType', boosttest_patterns)
-
+        self._SetXunit("BoostTestJunitHudsonTestType", boosttest_patterns)
 
     def SetBuildBatchCommands(self, build_batch_commands):
         for command in build_batch_commands:
@@ -173,61 +164,60 @@ def SetBuildBatchCommands(self, build_batch_commands):
                 self.SetBuildBatchCommands(command)
             else:
                 # batch files must have \r\n as EOL or weird bugs happen (jenkins web ui add \r).
-                self.xml['builders/hudson.tasks.BatchFile+/command'] = command.replace('\n', '\r\n')
-
+                self.xml["builders/hudson.tasks.BatchFile+/command"] = command.replace(
+                    "\n", "\r\n"
+                )
 
     def SetBuildShellCommands(self, build_shell_commands):
         for command in build_shell_commands:
             if isinstance(command, list):
                 self.SetBuildShellCommands(command)
             else:
-                self.xml['builders/hudson.tasks.Shell+/command'] = command
-
+                self.xml["builders/hudson.tasks.Shell+/command"] = command
 
     def SetBuildPythonCommands(self, build_shell_commands):
         for command in build_shell_commands:
             if isinstance(command, list):
                 self.SetBuildPythonCommands(command)
             else:
-                self.xml['builders/hudson.plugins.python.Python+/command'] = command
-
+                self.xml["builders/hudson.plugins.python.Python+/command"] = command
 
     def SetCron(self, schedule):
-        self.xml['triggers/hudson.triggers.TimerTrigger/spec'] = schedule
-
+        self.xml["triggers/hudson.triggers.TimerTrigger/spec"] = schedule
 
     def SetDescriptionRegex(self, description_regex):
-        description_setter = self.xml['publishers/hudson.plugins.descriptionsetter.DescriptionSetterPublisher']
-        description_setter['regexp'] = description_regex
-        description_setter['regexpForFailed'] = description_regex
-        description_setter['setForMatrix'] = xmls(False)
-
+        description_setter = self.xml[
+            "publishers/hudson.plugins.descriptionsetter.DescriptionSetterPublisher"
+        ]
+        description_setter["regexp"] = description_regex
+        description_setter["regexpForFailed"] = description_regex
+        description_setter["setForMatrix"] = xmls(False)
 
     def SetDisplayName(self, display_name):
-        self.xml['displayName'] = display_name
-
+        self.xml["displayName"] = display_name
 
     def SetEmailNotification(self, notification_info):
-        mailer = self.xml['publishers/hudson.tasks.Mailer']
+        mailer = self.xml["publishers/hudson.tasks.Mailer"]
 
         # Handle short mode where user only gives a list of recipients
         if isinstance(notification_info, str):
-            notification_info = {'recipients' : notification_info}
+            notification_info = {"recipients": notification_info}
 
-        mailer['recipients'] = notification_info.pop('recipients')
+        mailer["recipients"] = notification_info.pop("recipients")
 
-        notify_every_build = notification_info.pop('notify_every_build', xmls(False))
-        if notify_every_build in ['False', 'false']:
-            mailer['dontNotifyEveryUnstableBuild'] = xmls(True)
+        notify_every_build = notification_info.pop("notify_every_build", xmls(False))
+        if notify_every_build in ["False", "false"]:
+            mailer["dontNotifyEveryUnstableBuild"] = xmls(True)
         else:
-            mailer['dontNotifyEveryUnstableBuild'] = xmls(False)
-        mailer['sendToIndividuals'] = xmls(notification_info.pop('notify_individuals', xmls(False)))
-
-        self._CheckUnknownOptions('email_notification', notification_info)
+            mailer["dontNotifyEveryUnstableBuild"] = xmls(False)
+        mailer["sendToIndividuals"] = xmls(
+            notification_info.pop("notify_individuals", xmls(False))
+        )
 
+        self._CheckUnknownOptions("email_notification", notification_info)
 
     def SetGit(self, git_options, git_xml=None):
-        '''
+        """
         Sets git options
 
         :param dict git_options:
@@ -236,11 +226,11 @@ def SetGit(self, git_options, git_xml=None):
         :param None|xml_factory._xml_factory.XmlFactory git_xml:
             Target XmlFactory object to set options.
             If None, will use the main project's Xml (`self.git`)
-        '''
+        """
         if git_xml is None:
             git_xml = self.git
 
-        git_xml['configVersion'] = '2'
+        git_xml["configVersion"] = "2"
 
         def _Set(option, xml_path, default=None):
             value = git_options.pop(option, default)
@@ -250,366 +240,421 @@ def _Set(option, xml_path, default=None):
 
         # Git branch option is set in many places
         branch_paths = [
-            'branches/hudson.plugins.git.BranchSpec/name',  # Branch being built
-            'extensions/hudson.plugins.git.extensions.impl.LocalBranch/localBranch',  # Checkout to local branch (GitPlugin 2.0+)
-            'localBranch',  # Checkout to local branch (GitPlugin 1.5)
+            "branches/hudson.plugins.git.BranchSpec/name",  # Branch being built
+            "extensions/hudson.plugins.git.extensions.impl.LocalBranch/localBranch",  # Checkout to local branch (GitPlugin 2.0+)
+            "localBranch",  # Checkout to local branch (GitPlugin 1.5)
         ]
 
         # Set all options --------------------------------------------------------------------------
         # Try to obtain a default target_dir based on repository name
-        if 'url' in git_options:
+        if "url" in git_options:
             from jobs_done10.repository import Repository
-            repository = Repository(url=git_options['url'])
-            _Set('target_dir', 'relativeTargetDir', default=repository.name)
-        else:
-            _Set('target_dir', 'relativeTargetDir')
 
-        _Set('remote', 'userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/name')
-        _Set('refspec', 'userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/refspec')
-        _Set('url', 'userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url')
-        _Set('branch', branch_paths)
-        _Set('recursive_submodules', 'extensions/hudson.plugins.git.extensions.impl.SubmoduleOption/recursiveSubmodules')
-        _Set('shallow_clone', 'extensions/hudson.plugins.git.extensions.impl.CloneOption/shallow')
-        _Set('reference', 'extensions/hudson.plugins.git.extensions.impl.CloneOption/reference')
-        _Set('timeout', 'extensions/hudson.plugins.git.extensions.impl.CloneOption/timeout')
+            repository = Repository(url=git_options["url"])
+            _Set("target_dir", "relativeTargetDir", default=repository.name)
+        else:
+            _Set("target_dir", "relativeTargetDir")
+
+        _Set("remote", "userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/name")
+        _Set("refspec", "userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/refspec")
+        _Set("url", "userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url")
+        _Set("branch", branch_paths)
+        _Set(
+            "recursive_submodules",
+            "extensions/hudson.plugins.git.extensions.impl.SubmoduleOption/recursiveSubmodules",
+        )
+        _Set(
+            "shallow_clone",
+            "extensions/hudson.plugins.git.extensions.impl.CloneOption/shallow",
+        )
+        _Set(
+            "reference",
+            "extensions/hudson.plugins.git.extensions.impl.CloneOption/reference",
+        )
+        _Set(
+            "timeout",
+            "extensions/hudson.plugins.git.extensions.impl.CloneOption/timeout",
+        )
 
         # just accessing attribute tag in the xml object will create a tag without text
         bool_options = [
-            ('clean_checkout', 'extensions/hudson.plugins.git.extensions.impl.CleanCheckout', 'true'),
-            ('lfs', 'extensions/hudson.plugins.git.extensions.impl.GitLFSPull', 'true'),
+            (
+                "clean_checkout",
+                "extensions/hudson.plugins.git.extensions.impl.CleanCheckout",
+                "true",
+            ),
+            ("lfs", "extensions/hudson.plugins.git.extensions.impl.GitLFSPull", "true"),
         ]
         for option_name, tag_path, default_value in bool_options:
             value = git_options.pop(option_name, default_value)
-            if value == 'true':
+            if value == "true":
                 # noinspection PyStatementEffect
                 git_xml[tag_path]
 
-
-        self._CheckUnknownOptions('git', git_options)
-
+        self._CheckUnknownOptions("git", git_options)
 
     def SetJunitPatterns(self, junit_patterns):
-        self._SetXunit('JUnitType', junit_patterns)
-
+        self._SetXunit("JUnitType", junit_patterns)
 
     def SetJsunitPatterns(self, jsunit_patterns):
-        self._SetXunit('JSUnitPluginType', jsunit_patterns)
-
+        self._SetXunit("JSUnitPluginType", jsunit_patterns)
 
     def SetLabelExpression(self, label_expression):
-        self.xml['assignedNode'] = label_expression
-
+        self.xml["assignedNode"] = label_expression
 
     def SetNotifyStash(self, args):
-        notifier = self.xml['publishers/org.jenkinsci.plugins.stashNotifier.StashNotifier']
+        notifier = self.xml[
+            "publishers/org.jenkinsci.plugins.stashNotifier.StashNotifier"
+        ]
 
         if isinstance(args, str):
             # Happens when no parameter is given, we just set the URL and assume that
             # username/password if the default configuration set in Jenkins server
-            notifier['stashServerBaseUrl'] = args
+            notifier["stashServerBaseUrl"] = args
         else:  # dict
-            notifier['stashServerBaseUrl'] = args.pop('url')
-            notifier['stashUserName'] = args.pop('username', '')
-            notifier['stashUserPassword'] = args.pop('password', '')
-
-            self._CheckUnknownOptions('notify_stash', args)
+            notifier["stashServerBaseUrl"] = args.pop("url")
+            notifier["stashUserName"] = args.pop("username", "")
+            notifier["stashUserPassword"] = args.pop("password", "")
 
+            self._CheckUnknownOptions("notify_stash", args)
 
     def SetParameters(self, parameters):
-        parameters_xml = self.xml['properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions']
+        parameters_xml = self.xml[
+            "properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions"
+        ]
         for i_parameter in parameters:
-            for name, j_dict  in i_parameter.items():
-                if name == 'choice':
-                    p = parameters_xml['hudson.model.ChoiceParameterDefinition+']
-                    p['choices@class'] = 'java.util.Arrays$ArrayList'
-                    p['choices/a@class'] = 'string-array'
-                    for k_choice in j_dict['choices']:
-                        p['choices/a/string+'] = k_choice
-
-                elif name == 'string':
-                    p = parameters_xml['hudson.model.StringParameterDefinition+']
-                    if j_dict['default']:
-                        p['defaultValue'] = j_dict['default']
+            for name, j_dict in i_parameter.items():
+                if name == "choice":
+                    p = parameters_xml["hudson.model.ChoiceParameterDefinition+"]
+                    p["choices@class"] = "java.util.Arrays$ArrayList"
+                    p["choices/a@class"] = "string-array"
+                    for k_choice in j_dict["choices"]:
+                        p["choices/a/string+"] = k_choice
+
+                elif name == "string":
+                    p = parameters_xml["hudson.model.StringParameterDefinition+"]
+                    if j_dict["default"]:
+                        p["defaultValue"] = j_dict["default"]
 
                 # Common options
-                p['name'] = j_dict['name']
-                p['description'] = j_dict['description']
-
+                p["name"] = j_dict["name"]
+                p["description"] = j_dict["description"]
 
     def SetScmPoll(self, schedule):
-        self.xml['triggers/hudson.triggers.SCMTrigger/spec'] = schedule
-
+        self.xml["triggers/hudson.triggers.SCMTrigger/spec"] = schedule
 
     def SetTimeout(self, timeout):
-        timeout_xml = self.xml['buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper']
-        timeout_xml['timeoutMinutes'] = str(timeout)
-        timeout_xml['failBuild'] = xmls(True)
-
+        timeout_xml = self.xml[
+            "buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper"
+        ]
+        timeout_xml["timeoutMinutes"] = str(timeout)
+        timeout_xml["failBuild"] = xmls(True)
 
     def SetTimeoutNoActivity(self, timeout):
-        timeout_xml = self.xml['buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper']
-        timeout_xml['strategy@class'] = 'hudson.plugins.build_timeout.impl.NoActivityTimeOutStrategy'
-        timeout_xml['strategy/timeoutSecondsString'] = timeout
-        timeout_xml['operationList/hudson.plugins.build__timeout.operations.FailOperation']
-
+        timeout_xml = self.xml[
+            "buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper"
+        ]
+        timeout_xml[
+            "strategy@class"
+        ] = "hudson.plugins.build_timeout.impl.NoActivityTimeOutStrategy"
+        timeout_xml["strategy/timeoutSecondsString"] = timeout
+        timeout_xml[
+            "operationList/hudson.plugins.build__timeout.operations.FailOperation"
+        ]
 
     def SetCustomWorkspace(self, custom_workspace):
-        self.xml['customWorkspace'] = custom_workspace
-
+        self.xml["customWorkspace"] = custom_workspace
 
     def SetSlack(self, options):
-        room = options.get('room', 'general')
-
-        properties = self.xml['properties/jenkins.plugins.slack.SlackNotifier_-SlackJobProperty']
-        properties['@plugin'] = 'slack@1.2'
-        properties['room'] = '#' + room
-        properties['startNotification'] = 'true'
-        properties['notifySuccess'] = 'true'
-        properties['notifyAborted'] = 'true'
-        properties['notifyNotBuilt'] = 'true'
-        properties['notifyUnstable'] = 'true'
-        properties['notifyFailure'] = 'true'
-        properties['notifyBackToNormal'] = 'true'
-
-        publisher = self.xml['publishers/jenkins.plugins.slack.SlackNotifier']
-        publisher['@plugin'] = "slack@1.2"
-        publisher['teamDomain'] = options['team']
-        publisher['authToken'] = options['token']
-        publisher['buildServerUrl'] = options['url']
-        publisher['room'] = '#' + room
+        room = options.get("room", "general")
 
+        properties = self.xml[
+            "properties/jenkins.plugins.slack.SlackNotifier_-SlackJobProperty"
+        ]
+        properties["@plugin"] = "slack@1.2"
+        properties["room"] = "#" + room
+        properties["startNotification"] = "true"
+        properties["notifySuccess"] = "true"
+        properties["notifyAborted"] = "true"
+        properties["notifyNotBuilt"] = "true"
+        properties["notifyUnstable"] = "true"
+        properties["notifyFailure"] = "true"
+        properties["notifyBackToNormal"] = "true"
+
+        publisher = self.xml["publishers/jenkins.plugins.slack.SlackNotifier"]
+        publisher["@plugin"] = "slack@1.2"
+        publisher["teamDomain"] = options["team"]
+        publisher["authToken"] = options["token"]
+        publisher["buildServerUrl"] = options["url"]
+        publisher["room"] = "#" + room
 
     def SetNotification(self, options):
-        properties = self.xml['properties/com.tikal.hudson.plugins.notification.HudsonNotificationProperty']
-        properties['@plugin'] = 'notification@1.9'
-        endpoint = properties['endpoints/com.tikal.hudson.plugins.notification.Endpoint']
-        endpoint['protocol'] = options.get('protocol', 'HTTP')
-        endpoint['format'] = options.get('format', 'JSON')
-        endpoint['url'] = options['url']
-        endpoint['event'] = 'all'
-        endpoint['timeout'] = '30000'
-        endpoint['loglines'] = '1'
-
+        properties = self.xml[
+            "properties/com.tikal.hudson.plugins.notification.HudsonNotificationProperty"
+        ]
+        properties["@plugin"] = "notification@1.9"
+        endpoint = properties[
+            "endpoints/com.tikal.hudson.plugins.notification.Endpoint"
+        ]
+        endpoint["protocol"] = options.get("protocol", "HTTP")
+        endpoint["format"] = options.get("format", "JSON")
+        endpoint["url"] = options["url"]
+        endpoint["event"] = "all"
+        endpoint["timeout"] = "30000"
+        endpoint["loglines"] = "1"
 
     def SetConsoleColor(self, option):
-        wrapper_xpath = 'buildWrappers/hudson.plugins.ansicolor.AnsiColorBuildWrapper'
+        wrapper_xpath = "buildWrappers/hudson.plugins.ansicolor.AnsiColorBuildWrapper"
         ansi_color_wrapper = self.xml[wrapper_xpath]
 
         option = option.strip()
-        if option not in ('', 'xterm', 'vga', 'css', 'gnome-terminal'):
+        if option not in ("", "xterm", "vga", "css", "gnome-terminal"):
             msg = [
-                'Received unknown console_color option.',
-                'Received %s but expected one of xterm, vga, css, gnome-terminal.',
-                '(case sensitive, empty string ins interpreted as xterm)',
+                "Received unknown console_color option.",
+                "Received %s but expected one of xterm, vga, css, gnome-terminal.",
+                "(case sensitive, empty string ins interpreted as xterm)",
             ]
-            raise RuntimeError('\n'.join(msg))
+            raise RuntimeError("\n".join(msg))
         if len(option) == 0:
-            option = 'xterm'
-
-        ansi_color_wrapper['@plugin'] = 'ansicolor@0.4.2'
-        ansi_color_wrapper['colorMapName'] = option
+            option = "xterm"
 
+        ansi_color_wrapper["@plugin"] = "ansicolor@0.4.2"
+        ansi_color_wrapper["colorMapName"] = option
 
     def SetTimestamps(self, ignored):
-        wrapper_xpath = 'buildWrappers/hudson.plugins.timestamper.TimestamperBuildWrapper'
+        wrapper_xpath = (
+            "buildWrappers/hudson.plugins.timestamper.TimestamperBuildWrapper"
+        )
         wrapper = self.xml[wrapper_xpath]
-        wrapper['@plugin'] = 'timestamper@1.7.4'
-
+        wrapper["@plugin"] = "timestamper@1.7.4"
 
     def SetCoverage(self, options):
-        report_pattern = options.get('report_pattern')
+        report_pattern = options.get("report_pattern")
         if not report_pattern:
             raise ValueError("Report pattern is required by coverage")
 
         cobertura_publisher = self.xml[
-            'publishers/hudson.plugins.cobertura.CoberturaPublisher']
-        cobertura_publisher['@plugin'] = 'cobertura@1.9.7'
+            "publishers/hudson.plugins.cobertura.CoberturaPublisher"
+        ]
+        cobertura_publisher["@plugin"] = "cobertura@1.9.7"
         # This is a file name pattern that can be used to locate the cobertura xml report files
         # (for example with Maven2 use **/target/site/cobertura/coverage.xml). The path is relative
         # to the module root unless you have configured your SCM with multiple modules, in which
         # case it is relative to the workspace root. Note that the module root is SCM-specific,
         # and may not be the same as the workspace root. Cobertura must be configured to generate
         # XML reports for this plugin to function.
-        cobertura_publisher['coberturaReportFile'] = report_pattern
-        cobertura_publisher['onlyStable'] = 'false'  # Include only stable builds, i.e. exclude unstable and failed ones.
-        cobertura_publisher['failUnhealthy'] = 'true'  # fail builds if No coverage reports are found.
-        cobertura_publisher['failUnstable'] = 'true'  # Unhealthy projects will be failed.
-        cobertura_publisher['autoUpdateHealth'] = 'false'  # Unstable projects will be failed.
-        cobertura_publisher['autoUpdateStability'] = 'false'  # Auto update threshold for health on successful build.
-        cobertura_publisher['autoUpdateStability'] = 'false'  # Auto update threshold for stability on successful build.
-        cobertura_publisher['zoomCoverageChart'] = 'false'  # Zoom the coverage chart and crop area below the minimum and above the maximum coverage of the past reports.
-        cobertura_publisher['maxNumberOfBuilds'] = '0'  # Only graph the most recent N builds in the coverage chart, 0 disables the limit.
-        cobertura_publisher['sourceEncoding'] = 'UTF_8'  # Encoding when showing files.
+        cobertura_publisher["coberturaReportFile"] = report_pattern
+        cobertura_publisher[
+            "onlyStable"
+        ] = "false"  # Include only stable builds, i.e. exclude unstable and failed ones.
+        cobertura_publisher[
+            "failUnhealthy"
+        ] = "true"  # fail builds if No coverage reports are found.
+        cobertura_publisher[
+            "failUnstable"
+        ] = "true"  # Unhealthy projects will be failed.
+        cobertura_publisher[
+            "autoUpdateHealth"
+        ] = "false"  # Unstable projects will be failed.
+        cobertura_publisher[
+            "autoUpdateStability"
+        ] = "false"  # Auto update threshold for health on successful build.
+        cobertura_publisher[
+            "autoUpdateStability"
+        ] = "false"  # Auto update threshold for stability on successful build.
+        cobertura_publisher[
+            "zoomCoverageChart"
+        ] = "false"  # Zoom the coverage chart and crop area below the minimum and above the maximum coverage of the past reports.
+        cobertura_publisher[
+            "maxNumberOfBuilds"
+        ] = "0"  # Only graph the most recent N builds in the coverage chart, 0 disables the limit.
+        cobertura_publisher["sourceEncoding"] = "UTF_8"  # Encoding when showing files.
 
         def FormatMetricValue(metric):
             # It seems to use more places for precision reasons
             return str(int(metric) * 100000)
 
         def WriteMetrics(target_name, metrics_options, default):
-            metrics = cobertura_publisher['{}'.format(target_name)]
-            targets = metrics['targets']
-            targets['@class'] = 'enum-map'
-            targets['@enum-type'] = 'hudson.plugins.cobertura.targets.CoverageMetric'
-            entry = targets['entry+']
-            entry['hudson.plugins.cobertura.targets.CoverageMetric'] = 'METHOD'
-            entry['int'] = FormatMetricValue(metrics_options.get('method', default))
+            metrics = cobertura_publisher["{}".format(target_name)]
+            targets = metrics["targets"]
+            targets["@class"] = "enum-map"
+            targets["@enum-type"] = "hudson.plugins.cobertura.targets.CoverageMetric"
+            entry = targets["entry+"]
+            entry["hudson.plugins.cobertura.targets.CoverageMetric"] = "METHOD"
+            entry["int"] = FormatMetricValue(metrics_options.get("method", default))
 
-            entry = targets['entry+']
-            entry['hudson.plugins.cobertura.targets.CoverageMetric'] = 'LINE'
-            entry['int'] = FormatMetricValue(metrics_options.get('line', default))
+            entry = targets["entry+"]
+            entry["hudson.plugins.cobertura.targets.CoverageMetric"] = "LINE"
+            entry["int"] = FormatMetricValue(metrics_options.get("line", default))
 
-            entry = targets['entry+']
-            entry['hudson.plugins.cobertura.targets.CoverageMetric'] = 'CONDITIONAL'
-            entry['int'] = FormatMetricValue(metrics_options.get('conditional', default))
+            entry = targets["entry+"]
+            entry["hudson.plugins.cobertura.targets.CoverageMetric"] = "CONDITIONAL"
+            entry["int"] = FormatMetricValue(
+                metrics_options.get("conditional", default)
+            )
 
-        healthy_options = options.get('healthy', {})
-        WriteMetrics('healthyTarget', healthy_options, default=80)
+        healthy_options = options.get("healthy", {})
+        WriteMetrics("healthyTarget", healthy_options, default=80)
 
-        unhealthy_options = options.get('unhealthy', {})
-        WriteMetrics('unhealthyTarget', unhealthy_options, default=0)
-
-        failing_options = options.get('failing', {})
-        WriteMetrics('failingTarget', failing_options, default=0)
+        unhealthy_options = options.get("unhealthy", {})
+        WriteMetrics("unhealthyTarget", unhealthy_options, default=0)
 
+        failing_options = options.get("failing", {})
+        WriteMetrics("failingTarget", failing_options, default=0)
 
     def SetWarnings(self, options):
-        known_options = {'console', 'file'}
+        known_options = {"console", "file"}
         unknown_options = set(options) - known_options
         if unknown_options != set():
             msg = [
-                "Received unknown 'warnings' options: %s." % ', '.join(unknown_options),
-                "Expected at least one of these: %s." % ', '.join(known_options),
+                "Received unknown 'warnings' options: %s." % ", ".join(unknown_options),
+                "Expected at least one of these: %s." % ", ".join(known_options),
             ]
-            raise ValueError('\n'.join(msg))
+            raise ValueError("\n".join(msg))
         if len(options) == 0:
-            raise ValueError("Empty 'warnings' options. Expected at least one of these: %s." % (
-                    ', '.join(known_options)))
-
-        warnings_xml = self.xml['publishers/hudson.plugins.warnings.WarningsPublisher']
-        warnings_xml['@plugin'] = 'warnings@4.59'
-        warnings_xml['healthy']
-        warnings_xml['unHealthy']
-        warnings_xml['thresholdLimit'] = 'low'
-        warnings_xml['pluginName'] = '[WARNINGS]'
-        warnings_xml['defaultEncoding']
-        warnings_xml['canRunOnFailed'] = 'true'
-        warnings_xml['usePreviousBuildAsReference'] = 'false'
-        warnings_xml['useStableBuildAsReference'] = 'false'
-        warnings_xml['useDeltaValues'] = 'false'
-
-        thresholds_xml = warnings_xml['thresholds']
-        thresholds_xml['@plugin'] = 'analysis-core@1.82'
-        thresholds_xml['unstableTotalAll']
-        thresholds_xml['unstableTotalHigh']
-        thresholds_xml['unstableTotalNormal']
-        thresholds_xml['unstableTotalLow']
-        thresholds_xml['unstableNewAll']
-        thresholds_xml['unstableNewHigh']
-        thresholds_xml['unstableNewNormal']
-        thresholds_xml['unstableNewLow']
-        thresholds_xml['failedTotalAll']
-        thresholds_xml['failedTotalHigh']
-        thresholds_xml['failedTotalNormal']
-        thresholds_xml['failedTotalLow']
-        thresholds_xml['failedNewAll']
-        thresholds_xml['failedNewHigh']
-        thresholds_xml['failedNewNormal']
-        thresholds_xml['failedNewLow']
-
-        warnings_xml['shouldDetectModules'] = 'false'
-        warnings_xml['dontComputeNew'] = 'true'
-        warnings_xml['doNotResolveRelativePaths'] = 'false'
-        warnings_xml['includePattern']
-        warnings_xml['excludePattern']
-        warnings_xml['messagesPattern']
-
-        file_parsers_xml = warnings_xml['parserConfigurations']
-        for parser_options in options.get('file', []):
-            parser_xml = file_parsers_xml['hudson.plugins.warnings.ParserConfiguration+']
-            parser_xml['pattern'] = parser_options.get('file_pattern')
-            parser_xml['parserName'] = parser_options.get('parser')
-
-        console_parsers_xml = warnings_xml['consoleParsers']
-        for parser_options in options.get('console', []):
-            parser_xml = console_parsers_xml['hudson.plugins.warnings.ConsoleParser+']
-            parser_xml['parserName'] = parser_options.get('parser')
+            raise ValueError(
+                "Empty 'warnings' options. Expected at least one of these: %s."
+                % (", ".join(known_options))
+            )
+
+        warnings_xml = self.xml["publishers/hudson.plugins.warnings.WarningsPublisher"]
+        warnings_xml["@plugin"] = "warnings@4.59"
+        warnings_xml["healthy"]
+        warnings_xml["unHealthy"]
+        warnings_xml["thresholdLimit"] = "low"
+        warnings_xml["pluginName"] = "[WARNINGS]"
+        warnings_xml["defaultEncoding"]
+        warnings_xml["canRunOnFailed"] = "true"
+        warnings_xml["usePreviousBuildAsReference"] = "false"
+        warnings_xml["useStableBuildAsReference"] = "false"
+        warnings_xml["useDeltaValues"] = "false"
+
+        thresholds_xml = warnings_xml["thresholds"]
+        thresholds_xml["@plugin"] = "analysis-core@1.82"
+        thresholds_xml["unstableTotalAll"]
+        thresholds_xml["unstableTotalHigh"]
+        thresholds_xml["unstableTotalNormal"]
+        thresholds_xml["unstableTotalLow"]
+        thresholds_xml["unstableNewAll"]
+        thresholds_xml["unstableNewHigh"]
+        thresholds_xml["unstableNewNormal"]
+        thresholds_xml["unstableNewLow"]
+        thresholds_xml["failedTotalAll"]
+        thresholds_xml["failedTotalHigh"]
+        thresholds_xml["failedTotalNormal"]
+        thresholds_xml["failedTotalLow"]
+        thresholds_xml["failedNewAll"]
+        thresholds_xml["failedNewHigh"]
+        thresholds_xml["failedNewNormal"]
+        thresholds_xml["failedNewLow"]
+
+        warnings_xml["shouldDetectModules"] = "false"
+        warnings_xml["dontComputeNew"] = "true"
+        warnings_xml["doNotResolveRelativePaths"] = "false"
+        warnings_xml["includePattern"]
+        warnings_xml["excludePattern"]
+        warnings_xml["messagesPattern"]
+
+        file_parsers_xml = warnings_xml["parserConfigurations"]
+        for parser_options in options.get("file", []):
+            parser_xml = file_parsers_xml[
+                "hudson.plugins.warnings.ParserConfiguration+"
+            ]
+            parser_xml["pattern"] = parser_options.get("file_pattern")
+            parser_xml["parserName"] = parser_options.get("parser")
 
+        console_parsers_xml = warnings_xml["consoleParsers"]
+        for parser_options in options.get("console", []):
+            parser_xml = console_parsers_xml["hudson.plugins.warnings.ConsoleParser+"]
+            parser_xml["parserName"] = parser_options.get("parser")
 
     def SetTriggerJobs(self, options):
-        condition = options.get('condition', 'SUCCESS')
-        valid_conditions = ('SUCCESS', 'UNSTABLE', 'FAILED', 'ALWAYS')
+        condition = options.get("condition", "SUCCESS")
+        valid_conditions = ("SUCCESS", "UNSTABLE", "FAILED", "ALWAYS")
         if condition not in valid_conditions:
-            msg = 'Invalid value for condition: {!r}, expected one of {!r}'
+            msg = "Invalid value for condition: {!r}, expected one of {!r}"
             raise RuntimeError(msg.format(condition, valid_conditions))
-        xml_trigger = self.xml['publishers/hudson.plugins.parameterizedtrigger.BuildTrigger']
-        xml_trigger['@plugin'] = "parameterized-trigger@2.33"
-        xml_config = xml_trigger['configs/hudson.plugins.parameterizedtrigger.BuildTriggerConfig']
-        parameters = options.get('parameters', [])
-        xml_configs = xml_config['configs']
+        xml_trigger = self.xml[
+            "publishers/hudson.plugins.parameterizedtrigger.BuildTrigger"
+        ]
+        xml_trigger["@plugin"] = "parameterized-trigger@2.33"
+        xml_config = xml_trigger[
+            "configs/hudson.plugins.parameterizedtrigger.BuildTriggerConfig"
+        ]
+        parameters = options.get("parameters", [])
+        xml_configs = xml_config["configs"]
         if parameters:
-            xml_configs['hudson.plugins.parameterizedtrigger.PredefinedBuildParameters/properties'] = ' '.join(parameters)
+            xml_configs[
+                "hudson.plugins.parameterizedtrigger.PredefinedBuildParameters/properties"
+            ] = " ".join(parameters)
         else:
-            xml_configs['@class'] = 'empty-list'
-        xml_config['projects'] = ', '.join(options['names'])
-        xml_config['condition'] = condition
-
-        xml_config['triggerWithNoParameters'] = 'true' if not parameters else 'false'
-        xml_config['triggerFromChildProjects'] = 'false'
+            xml_configs["@class"] = "empty-list"
+        xml_config["projects"] = ", ".join(options["names"])
+        xml_config["condition"] = condition
 
+        xml_config["triggerWithNoParameters"] = "true" if not parameters else "false"
+        xml_config["triggerFromChildProjects"] = "false"
 
     # Internal functions ---------------------------------------------------------------------------
     def _SetXunit(self, xunit_type, patterns):
         # Set common xunit patterns
-        xunit = self.xml['publishers/xunit']
-        xunit['thresholds/org.jenkinsci.plugins.xunit.threshold.FailedThreshold/unstableThreshold'] = '0'
-        xunit['thresholds/org.jenkinsci.plugins.xunit.threshold.FailedThreshold/unstableNewThreshold'] = '0'
-        xunit['thresholdMode'] = '1'
+        xunit = self.xml["publishers/xunit"]
+        xunit[
+            "thresholds/org.jenkinsci.plugins.xunit.threshold.FailedThreshold/unstableThreshold"
+        ] = "0"
+        xunit[
+            "thresholds/org.jenkinsci.plugins.xunit.threshold.FailedThreshold/unstableNewThreshold"
+        ] = "0"
+        xunit["thresholdMode"] = "1"
 
         # Set patterns for the given type
-        xunit_type_xml = xunit['tools/' + xunit_type]
-        xunit_type_xml['pattern'] = ','.join(patterns)
-        xunit_type_xml['skipNoTestFiles'] = xmls(True)
-        xunit_type_xml['failIfNotNew'] = xmls(False)
-        xunit_type_xml['deleteOutputFiles'] = xmls(True)
-        xunit_type_xml['stopProcessingIfError'] = xmls(True)
+        xunit_type_xml = xunit["tools/" + xunit_type]
+        xunit_type_xml["pattern"] = ",".join(patterns)
+        xunit_type_xml["skipNoTestFiles"] = xmls(True)
+        xunit_type_xml["failIfNotNew"] = xmls(False)
+        xunit_type_xml["deleteOutputFiles"] = xmls(True)
+        xunit_type_xml["stopProcessingIfError"] = xmls(True)
 
         # Add a cleanup sequence to delete all test results when a build starts
-        cleanup = self.xml['buildWrappers/hudson.plugins.ws__cleanup.PreBuildCleanup']
+        cleanup = self.xml["buildWrappers/hudson.plugins.ws__cleanup.PreBuildCleanup"]
         for pattern in patterns:
-            pattern_tag = cleanup['patterns/hudson.plugins.ws__cleanup.Pattern+']
-            pattern_tag['pattern'] = pattern
-            pattern_tag['type'] = 'INCLUDE'
-
+            pattern_tag = cleanup["patterns/hudson.plugins.ws__cleanup.Pattern+"]
+            pattern_tag["pattern"] = pattern
+            pattern_tag["type"] = "INCLUDE"
 
     def _CheckUnknownOptions(self, configuration_name, options_dict):
         if len(options_dict) > 0:
-            raise RuntimeError('Received unknown %s options: %s' % (configuration_name, list(options_dict.keys())))
-
+            raise RuntimeError(
+                "Received unknown %s options: %s"
+                % (configuration_name, list(options_dict.keys()))
+            )
 
 
-#===================================================================================================
+# ===================================================================================================
 # Utils
-#===================================================================================================
+# ===================================================================================================
 def _AsXmlString(boolean):
-    '''
+    """
     :param bool boolean:
         True or False
 
     :return unicode:
         `boolean` representation as a string used by Jenkins XML
-    '''
+    """
     return str(boolean).lower()
-xmls = _AsXmlString
 
 
+xmls = _AsXmlString
+
 
-#===================================================================================================
+# ===================================================================================================
 # JenkinsJobPublisher
-#===================================================================================================
+# ===================================================================================================
 class JenkinsJobPublisher(object):
-    '''
+    """
     Publishes `JenkinsJob`s
-    '''
+    """
+
     # Times to retry on ProxyErrors
     RETRIES = 3
 
@@ -617,24 +662,24 @@ class JenkinsJobPublisher(object):
     RETRY_SLEEP = 1
 
     def __init__(self, repository, jobs):
-        '''
+        """
         :param Repository repository:
             Repository used for these jobs. Used to find other jobs in the same url/branch to be
             updated or deleted.
 
         :param list(JenkinsJob) jobs:
             List of jobs to be published.
-        '''
+        """
         for job in jobs:
-            assert job.repository == repository, +\
-                'All published jobs must belong to the given `repository`'
+            assert (
+                job.repository == repository
+            ), +"All published jobs must belong to the given `repository`"
 
         self.repository = repository
         self.jobs = dict((job.name, job) for job in jobs)
 
-
     def PublishToUrl(self, url, username=None, password=None):
-        '''
+        """
         Publishes new jobs, updated existing jobs, and delete jobs that belong to the same
         repository/branch but were not updated.
 
@@ -649,7 +694,7 @@ def PublishToUrl(self, url, username=None, password=None):
 
         :return tuple(list(unicode),list(unicode),list(unicode)):
             Tuple with lists of {new, updated, deleted} job names (sorted alphabetically)
-        '''
+        """
         import jenkins
 
         jenkins_api = jenkins.Jenkins(url, username, password)
@@ -665,15 +710,20 @@ def PublishToUrl(self, url, username=None, password=None):
 
         def retry(func, *args, **kwargs):
             from requests.exceptions import HTTPError
+
             for _ in range(self.RETRIES):
                 try:
                     func(*args, **kwargs)
                     break
                 except HTTPError as e:
                     http_error = e
-                    if http_error.response.status_code in (403, 502):  # 403 Forbidden, 502 Proxy error
+                    if http_error.response.status_code in (
+                        403,
+                        502,
+                    ):  # 403 Forbidden, 502 Proxy error
                         # This happens sometimes for no apparent reason, and we want to retry.
                         from time import sleep
+
                         sleep(self.RETRY_SLEEP)
                     else:
                         raise
@@ -693,22 +743,23 @@ def retry(func, *args, **kwargs):
 
         return list(map(sorted, (new_jobs, updated_jobs, deleted_jobs)))
 
-
     def PublishToDirectory(self, output_directory):
-        '''
+        """
         Publishes jobs to a directory. Each job creates a file with its name and xml contents.
 
         :param unicode output_directory:
              Target directory for outputting job .xmls
-        '''
+        """
         import os
+
         for job in self.jobs.values():
-            with io.open(os.path.join(output_directory, job.name), 'w', encoding='utf-8') as f:
+            with io.open(
+                os.path.join(output_directory, job.name), "w", encoding="utf-8"
+            ) as f:
                 f.write(job.xml)
 
-
     def _GetMatchingJobs(self, jenkins_api):
-        '''
+        """
         Filter jobs that belong to the same repository/branch as a `job` being published
 
         :param jenkins_api:
@@ -716,11 +767,11 @@ def _GetMatchingJobs(self, jenkins_api):
 
         :return set(unicode):
             Names of all Jenkins jobs that match `job` repository name and branch
-        '''
+        """
         matching_jobs = set()
 
-        common_prefix = self.repository.name + '-' + self.repository.branch
-        for jenkins_job in (x['name'] for x in jenkins_api.get_jobs()):
+        common_prefix = self.repository.name + "-" + self.repository.branch
+        for jenkins_job in (x["name"] for x in jenkins_api.get_jobs()):
             # Filter jobs that belong to this repository (this would be safer to do reading SCM
             # information, but a lot more expensive
             if not jenkins_job.startswith(common_prefix):
@@ -732,9 +783,8 @@ def _GetMatchingJobs(self, jenkins_api):
 
         return matching_jobs
 
-
     def _GetJenkinsJobBranch(self, jenkins_api, jenkins_job):
-        '''
+        """
         :param jenkins.Jenkins jenkins_api:
             Configured Jenkins API that gives access to Jenkins data at a host.
 
@@ -747,7 +797,7 @@ def _GetJenkinsJobBranch(self, jenkins_api, jenkins_job):
         .. note::
             This function was separated to make use of Memoize cacheing, avoiding multiple queries
             to the same jenkins job config.xml
-        '''
+        """
         from xml.etree import ElementTree
 
         # Read config to see if this job is in the same branch
@@ -759,12 +809,12 @@ def _GetJenkinsJobBranch(self, jenkins_api, jenkins_job):
         root = ElementTree.fromstring(config)
 
         # Try for single SCM
-        name_element = root.find('scm/branches/hudson.plugins.git.BranchSpec/name')
+        name_element = root.find("scm/branches/hudson.plugins.git.BranchSpec/name")
         if name_element is not None:
             return name_element.text.strip()
 
         # If the above was not found, we might be dealing with multiple repositories
-        scms = root.findall('scm/scms/hudson.plugins.git.GitSCM')
+        scms = root.findall("scm/scms/hudson.plugins.git.GitSCM")
 
         checked_urls = []
 
@@ -774,41 +824,49 @@ def _GetJenkinsJobBranch(self, jenkins_api, jenkins_job):
 
         # Account for the fact that ssh://example.com/repo and ssh://example.com/repo.git
         # refer to the same repo
-        if not repo_lower.endswith('.git'):
-            repo_urls.append(repo_lower + '.git')
+        if not repo_lower.endswith(".git"):
+            repo_urls.append(repo_lower + ".git")
 
         # Process them all until we find the SCM for the correct repository
         for scm in scms:
-            url = scm.find('userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url').text.strip()
+            url = scm.find(
+                "userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url"
+            ).text.strip()
             checked_urls.append(url)
 
             urls = [url.lower()]
             # Same as above (account for urls ending with '.git')
-            if not url.endswith('.git'):
-                urls.append(url.lower() + '.git')
+            if not url.endswith(".git"):
+                urls.append(url.lower() + ".git")
 
             if any(url in repo_urls for url in urls):
-                return scm.find('branches/hudson.plugins.git.BranchSpec/name').text.strip()
+                return scm.find(
+                    "branches/hudson.plugins.git.BranchSpec/name"
+                ).text.strip()
 
         error_msg = [
-            'Could not find SCM for repository "%s" in job "%s"' % (self.repository.url, jenkins_job),
+            'Could not find SCM for repository "%s" in job "%s"'
+            % (self.repository.url, jenkins_job),
             'The local repository origin is set to "%s".' % (self.repository.url,),
-            'And the possible matches are:',
+            "And the possible matches are:",
         ]
-        error_msg.extend([' - %s' % (url,) for url in checked_urls])
-        error_msg.extend([
-            '',
-            'If needed a repository origin url can be set with "git remote set-url origin <URL>".',
-        ])
-        raise RuntimeError('\n'.join(error_msg))
-
+        error_msg.extend([" - %s" % (url,) for url in checked_urls])
+        error_msg.extend(
+            [
+                "",
+                'If needed a repository origin url can be set with "git remote set-url origin <URL>".',
+            ]
+        )
+        raise RuntimeError("\n".join(error_msg))
 
 
-#===================================================================================================
+# ===================================================================================================
 # Actions for common uses of Jenkins classes
-#===================================================================================================
-def UploadJobsFromFile(repository, jobs_done_file_contents, url, username=None, password=None):
-    '''
+# ===================================================================================================
+def UploadJobsFromFile(
+    repository, jobs_done_file_contents, url, username=None, password=None
+):
+    """
     :param repository:
         .. seealso:: GetJobsFromFile
 
@@ -827,16 +885,15 @@ def UploadJobsFromFile(repository, jobs_done_file_contents, url, username=None,
     :returns:
         .. seealso:: JenkinsJobPublisher.PublishToUrl
 
-    '''
+    """
     jobs = GetJobsFromFile(repository, jobs_done_file_contents)
     publisher = JenkinsJobPublisher(repository, jobs)
 
     return publisher.PublishToUrl(url, username, password)
 
 
-
-def GetJobsFromDirectory(directory='.'):
-    '''
+def GetJobsFromDirectory(directory="."):
+    """
     Looks in a directory for a jobs_done file and git repository information to create jobs.
 
     :param directory:
@@ -846,25 +903,36 @@ def GetJobsFromDirectory(directory='.'):
         Repository information for the given directory, and jobs obtained from this directory.
 
         .. seealso:: GetJobsFromFile
-    '''
+    """
     from jobs_done10.jobs_done_job import JOBS_DONE_FILENAME
     from jobs_done10.repository import Repository
     import os
 
     from subprocess import check_output
-    url = check_output('git config --local --get remote.origin.url', shell=True, cwd=directory).strip().decode('UTF-8')
-    branches = check_output('git branch', shell=True, cwd=directory).strip().decode('UTF-8')
+
+    url = (
+        check_output(
+            "git config --local --get remote.origin.url", shell=True, cwd=directory
+        )
+        .strip()
+        .decode("UTF-8")
+    )
+    branches = (
+        check_output("git branch", shell=True, cwd=directory).strip().decode("UTF-8")
+    )
     for branch in branches.splitlines():
         branch = branch.strip()
-        if '*' in branch:  # Current branch
-            branch = branch.split(' ', 1)[1]
+        if "*" in branch:  # Current branch
+            branch = branch.split(" ", 1)[1]
             break
     else:
-        raise RuntimeError('Error parsing output from git branch')
+        raise RuntimeError("Error parsing output from git branch")
 
     repository = Repository(url=url, branch=branch)
     try:
-        with io.open(os.path.join(directory, JOBS_DONE_FILENAME), encoding='utf-8') as f:
+        with io.open(
+            os.path.join(directory, JOBS_DONE_FILENAME), encoding="utf-8"
+        ) as f:
             jobs_done_file_contents = f.read()
     except IOError:
         jobs_done_file_contents = None
@@ -872,9 +940,8 @@ def GetJobsFromDirectory(directory='.'):
     return repository, GetJobsFromFile(repository, jobs_done_file_contents)
 
 
-
 def GetJobsFromFile(repository, jobs_done_file_contents):
-    '''
+    """
     Creates jobs from repository information and a jobs_done file.
 
     :param Repository repository:
@@ -884,7 +951,7 @@ def GetJobsFromFile(repository, jobs_done_file_contents):
         .. seealso:: JobsDoneJob.CreateFromYAML
 
     :return set(JenkinsJob)
-    '''
+    """
     from jobs_done10.job_generator import JobGeneratorConfigurator
     from jobs_done10.jobs_done_job import JobsDoneJob
 
diff --git a/src/jobs_done10/job_generator.py b/src/jobs_done10/job_generator.py
index 6316cc9..e3dcb6e 100644
--- a/src/jobs_done10/job_generator.py
+++ b/src/jobs_done10/job_generator.py
@@ -1,11 +1,8 @@
-
-
-
-#===================================================================================================
+# ===================================================================================================
 # IJobGenerator
-#===================================================================================================
+# ===================================================================================================
 class IJobGenerator(object):
-    '''
+    """
     Interface for job generators.
 
     These generators are responsible for creating continuous integration jobs for any particular
@@ -25,23 +22,21 @@ class IJobGenerator(object):
 
     .. seealso::
         http://en.wikipedia.org/wiki/Builder_pattern
-    '''
+    """
 
     def Reset(self):
-        '''
+        """
         Resets all configurations made to this generator.
-        '''
-
+        """
 
     def SetRepository(self, repository):
-        '''
+        """
         :param Repository repository:
             Repository information for jobs created by this generator.
-        '''
-
+        """
 
     def SetMatrix(self, matrix, matrix_row):
-        '''
+        """
         Sets current matrix and matrix_row of this job.
 
         :param dict(unicode,list(unicode)) matrix:
@@ -51,22 +46,22 @@ def SetMatrix(self, matrix, matrix_row):
         :param dict(unicode,unicode) matrix_row:
             .. seealso::
                 JobsDoneJob
-        '''
+        """
 
 
-#===================================================================================================
+# ===================================================================================================
 # JobGeneratorConfigurator
-#===================================================================================================
+# ===================================================================================================
 class JobGeneratorConfigurator(object):
-    '''
+    """
     Class used to configure `IJobGenerator`s using `JobsDoneJob`.
 
     .. seealso:: IJobGenerator
-    '''
+    """
 
     @classmethod
     def Configure(cls, generator, jobs_done_job):
-        '''
+        """
         This simply iterates over data and calls a series of functions in a generator. Functions
         called are determined by options in `jobs_done_job` by converting the option names to camel case.
             e.g.: option 'junit_patterns' will trigger a call to generator.SetJunitPatterns
@@ -76,7 +71,7 @@ def Configure(cls, generator, jobs_done_job):
 
         :param JobsDoneJob jobs_done_job:
             Will be used to extract options that must be configured in `generator`
-        '''
+        """
         generator.SetRepository(jobs_done_job.repository)
         generator.Reset()
         generator.SetMatrix(jobs_done_job.matrix, jobs_done_job.matrix_row)
@@ -87,29 +82,38 @@ def Configure(cls, generator, jobs_done_job):
                 continue  # Skip unset options
 
             # Find function name associated with the option being processed
-            generator_function_name = 'Set' + option.title().replace('_', '')
+            generator_function_name = "Set" + option.title().replace("_", "")
 
             # Obtain and call that function with the option value
             try:
                 generator_function = getattr(generator, generator_function_name)
             except AttributeError:
-                raise JobGeneratorAttributeError(generator, generator_function_name, option)
+                raise JobGeneratorAttributeError(
+                    generator, generator_function_name, option
+                )
 
             generator_function(option_value)
 
         return generator
 
 
-
-#===================================================================================================
+# ===================================================================================================
 # JobGeneratorAttributeError
-#===================================================================================================
+# ===================================================================================================
 class JobGeneratorAttributeError(AttributeError):
-    '''
+    """
     Raised when trying to access a generator function that is not implemented.
-    '''
+    """
+
     def __init__(self, generator, attribute, jobs_done_job_option):
-        message = '%s "%s" cannot handle option "%s" (could not find function "%s").' % \
-            (IJobGenerator.__name__, generator.__class__.__name__, jobs_done_job_option, attribute)
+        message = (
+            '%s "%s" cannot handle option "%s" (could not find function "%s").'
+            % (
+                IJobGenerator.__name__,
+                generator.__class__.__name__,
+                jobs_done_job_option,
+                attribute,
+            )
+        )
 
         AttributeError.__init__(self, message)
diff --git a/src/jobs_done10/jobs_done_job.py b/src/jobs_done10/jobs_done_job.py
index bd84c83..61f19a3 100644
--- a/src/jobs_done10/jobs_done_job.py
+++ b/src/jobs_done10/jobs_done_job.py
@@ -1,5 +1,3 @@
-
-
 import io
 
 import yaml
@@ -7,18 +5,18 @@
 
 # Name of jobs_done file, repositories must contain this file in their root dir to be able to
 # create jobs.
-JOBS_DONE_FILENAME = '.jobs_done.yaml'
+JOBS_DONE_FILENAME = ".jobs_done.yaml"
 
 
-#===================================================================================================
+# ===================================================================================================
 # JobsDoneJob
-#===================================================================================================
+# ===================================================================================================
 class JobsDoneJob(object):
-    '''
+    """
     Represents a jobs_done job, parsed from a jobs_done file.
 
     This is a generic representation, not related to any specific continuous integration tool.
-    '''
+    """
 
     # Options that should be forwarded to generators. These are set in JobsDoneJob instances
     # after parsing (setattr(option_name, self, value)), and are available as object fields
@@ -26,28 +24,21 @@ class JobsDoneJob(object):
     # NOTE TO DEVELOPERS: try to keep options in alphabetical order to simplify maintenance
     GENERATOR_OPTIONS = {
         # Additional repositories to be used in a job.
-        'additional_repositories': list,
-
+        "additional_repositories": list,
         # Job authentication token used to execute it remotely.
-        'auth_token': str,
-
+        "auth_token": str,
         # Patterns to match when looking for boosttest results.
-        'boosttest_patterns': list,
-
+        "boosttest_patterns": list,
         # Batch script commands used to build a project.
-        'build_batch_commands': list,
-
+        "build_batch_commands": list,
         # Shell script commands used to build a project.
-        'build_shell_commands': list,
-
+        "build_shell_commands": list,
         # Python commands used to build a project.
-        'build_python_commands': list,
-
+        "build_python_commands": list,
         # Process job output to handle ansi color escapes.
         # Should be one of xterm (default if empty), vga, css, gnome-terminal.
         # Requires https://wiki.jenkins-ci.org/display/JENKINS/AnsiColor+Plugin
-        'console_color': str,
-
+        "console_color": str,
         # Configures coverage in a CI job
         #
         # On Jenkins, this requires [Cobertura Plugin](
@@ -66,79 +57,61 @@ class JobsDoneJob(object):
         # * failing: dict|None
         #   Defines threshold necessary for build to fail. Can have keys `method`, `line`
         #   and `conditional`. If key is omitted it defaults to 0.
-        'coverage': dict,
-
+        "coverage": dict,
         # Time based triggers for job (Jenkins)
-        'cron': str,
-
+        "cron": str,
         # Custom workspace. To maintain the same location as the default workspace prefix it with
         # "workspace/"
-        'custom_workspace': str,
-
+        "custom_workspace": str,
         # Regex pattern to be matched from a job output and used as job description. (Jenkins)
         # Requires https://wiki.jenkins-ci.org/display/JENKINS/Description+Setter+Plugin
-        'description_regex': str,
-
+        "description_regex": str,
         # The format for the job display name.
-        'display_name': str,
-
+        "display_name": str,
         # Emails to be sent out for failed builds
-        'email_notification': (dict, str),
-
+        "email_notification": (dict, str),
         # Additional git options.
         # Uses same options available for git repos under `additional_repositories`
-        'git': dict,
-
+        "git": dict,
         # List of patterns to match when looking for jsunit test results.
         # Requires https://wiki.jenkins-ci.org/display/JENKINS/JSUnit+plugin
-        'jsunit_patterns': list,
-
+        "jsunit_patterns": list,
         # List of patterns to match when looking for junit test results.
-        'junit_patterns': list,
-
+        "junit_patterns": list,
         # A "label expression" that is used to match slave nodes.
-        'label_expression': str,
-
+        "label_expression": str,
         # Notification (HTTP/JSON)
         # https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin
         # * protocol: HTTP (optional)
         # * format: JSON (optional)
         # * url: https://requestb.in/12345678
-        'notification': dict,
-
+        "notification": dict,
         # Notifies Stash when a build passes
         # Requires https://wiki.jenkins-ci.org/display/JENKINS/StashNotifier+Plugin
         # e.g.
         #    notify_stash = {'url' : 'stash.com', 'username' : 'user', 'password' : 'pass'}
-        'notify_stash': (dict, str),
-
+        "notify_stash": (dict, str),
         # Definition of parameters available to this job.
         # Uses jenkins-job-builder syntax parsed by yaml.
         # .. seealso:: http://ci.openstack.org/jenkins-job-builder/parameters.html
         #
         # e.g.
         #     parameters = [{'choices' : ['1', '2'], 'name' : 'my_param'}]
-        'parameters': list,
-
+        "parameters": list,
         # Poll SCM for changes and trigger jobs based on a schedule (Jenkins)
-        'scm_poll': str,
-
+        "scm_poll": str,
         # Slack notification
         # * room: general
         # * token: 123123123123123123123
         # * url: http://eden.esss.com.br/jenkins
-        'slack': dict,
-
+        "slack": dict,
         # Job timeout in minutes
-        'timeout': str,
-
+        "timeout": str,
         # Job timeout in seconds without console activity
-        'timeout_no_activity' : str,
-
+        "timeout_no_activity": str,
         # Enable timestamps on the build output
         # https://wiki.jenkins-ci.org/display/JENKINS/Timestamper
-        'timestamps': str,
-
+        "timestamps": str,
         # Configures parsing of warnings and static analysis in a CI job
         #
         # On Jenkins, this requires [Warnings Plugin](
@@ -156,8 +129,7 @@ class JobsDoneJob(object):
         #     - parser: CodeAnalysis
         #       file_pattern: *.codeanalysis
         # ```
-        'warnings': dict,
-
+        "warnings": dict,
         # Trigger other jobs after this jobs finishes
         # On Jenkins this requires the [Parameterized Trigger Plugin]
         # (https://wiki.jenkins.io/display/JENKINS/Parameterized+Trigger+Plugin) to work.
@@ -172,71 +144,68 @@ class JobsDoneJob(object):
         #     - PARAM1=VALUE1
         #     - PARAM2=VALUE2
         # ```
-        'trigger_jobs': dict,
+        "trigger_jobs": dict,
     }
 
     # All parsed options
     PARSEABLE_OPTIONS = GENERATOR_OPTIONS.copy()
-    PARSEABLE_OPTIONS.update({
-        # list(unicode) branch_patterns:
-        #    A list of regexes to matcvh against branch names.
-        #    Jobs for a branch will only be created if any of this pattern matches that name.
-        #    .. note:: Uses python `re` syntax.
-        'branch_patterns':list,
-
-        # unicode exclude:
-        #     Determines if a job should be excluded (usually used with matrix flags)
-        #     Defaults to 'false'
-        'exclude': str,
-
-        # unicode ignore_unmatchable:
-        #    If 'true', will not raise errors when an unmatchable condition is found.
-        #    Defaults to 'false'
-        'ignore_unmatchable':str,
-
-        # dict matrix:
-        #     A dict that represents all possible job combinations created from this file.
-        #
-        #     When a jobs_done file is parsed, it can contain variables that form a matrix. For each
-        #     possible combination of these variables, a JobsDoneJob class is created. They can be used
-        #     for things such as creating jobs for multiple platforms from a single JobsDoneJob.
-        #
-        #     For example, if our file describes this matrix:
-        #         matrix:
-        #           planet:
-        #           - earth
-        #           - mars
-        #
-        #           moon:
-        #           - europa
-        #           - ganymede
-        #
-        #     This file's `matrix` will be:
-        #         {'planet' : ['earth', 'mars], 'moon' : ['europa', 'ganymede']}
-        #
-        #     This file's `matrix_row` will have one of these values (one for each JobsDoneJob created):
-        #         {'planet' : 'earth', 'moon' : 'europa'}
-        #         {'planet' : 'earth', 'moon' : 'ganymede'}
-        #         {'planet' : 'mars', 'moon' : 'europa'}
-        #         {'planet' : 'mars', 'moon' : 'ganymede'}
-        'matrix':dict,
-    })
-
+    PARSEABLE_OPTIONS.update(
+        {
+            # list(unicode) branch_patterns:
+            #    A list of regexes to matcvh against branch names.
+            #    Jobs for a branch will only be created if any of this pattern matches that name.
+            #    .. note:: Uses python `re` syntax.
+            "branch_patterns": list,
+            # unicode exclude:
+            #     Determines if a job should be excluded (usually used with matrix flags)
+            #     Defaults to 'false'
+            "exclude": str,
+            # unicode ignore_unmatchable:
+            #    If 'true', will not raise errors when an unmatchable condition is found.
+            #    Defaults to 'false'
+            "ignore_unmatchable": str,
+            # dict matrix:
+            #     A dict that represents all possible job combinations created from this file.
+            #
+            #     When a jobs_done file is parsed, it can contain variables that form a matrix. For each
+            #     possible combination of these variables, a JobsDoneJob class is created. They can be used
+            #     for things such as creating jobs for multiple platforms from a single JobsDoneJob.
+            #
+            #     For example, if our file describes this matrix:
+            #         matrix:
+            #           planet:
+            #           - earth
+            #           - mars
+            #
+            #           moon:
+            #           - europa
+            #           - ganymede
+            #
+            #     This file's `matrix` will be:
+            #         {'planet' : ['earth', 'mars], 'moon' : ['europa', 'ganymede']}
+            #
+            #     This file's `matrix_row` will have one of these values (one for each JobsDoneJob created):
+            #         {'planet' : 'earth', 'moon' : 'europa'}
+            #         {'planet' : 'earth', 'moon' : 'ganymede'}
+            #         {'planet' : 'mars', 'moon' : 'europa'}
+            #         {'planet' : 'mars', 'moon' : 'ganymede'}
+            "matrix": dict,
+        }
+    )
 
     def __init__(self):
-        '''
+        """
         :ivar dict(unicode,unicode) matrix_row:
             A dict that represents a single row from this file's `matrix`.
 
             .. seealso:: `matrix`@PARSEABLE_OPTIONS
-        '''
+        """
         self.matrix_row = None
 
         # Initialize known options with None
         for option_name in self.PARSEABLE_OPTIONS:
             setattr(self, option_name, None)
 
-
     @classmethod
     def CreateFromYAML(cls, yaml_contents, repository):
         '''
@@ -294,38 +263,44 @@ def CreateFromYAML(cls, yaml_contents, repository):
         # Load yaml
         jd_data = yaml.load(yaml_contents, Loader=yaml.loader.BaseLoader)
         if not jd_data:
-            raise ValueError('Could not parse anything from .yaml contents')
+            raise ValueError("Could not parse anything from .yaml contents")
 
         # Search for unknown options and type errors
         for option_name, option_value in jd_data.items():
-            option_name = option_name.rsplit(':', 1)[-1]
+            option_name = option_name.rsplit(":", 1)[-1]
             if option_name not in JobsDoneJob.PARSEABLE_OPTIONS:
                 raise UnknownJobsDoneFileOption(option_name)
 
             obtained_type = type(option_value)
             from jobs_done10.common import AsList
+
             expected_types = AsList(JobsDoneJob.PARSEABLE_OPTIONS[option_name])
             if obtained_type not in expected_types:
-                raise JobsDoneFileTypeError(option_name, obtained_type, expected_types, option_value)
+                raise JobsDoneFileTypeError(
+                    option_name, obtained_type, expected_types, option_value
+                )
 
         # List all possible matrix_rows
-        matrix_rows = cls._MatrixRow.CreateFromDict(jd_data.get('matrix', {}))
+        matrix_rows = cls._MatrixRow.CreateFromDict(jd_data.get("matrix", {}))
 
-        ignore_unmatchable = Boolean(jd_data.get('ignore_unmatchable', 'false'))
+        ignore_unmatchable = Boolean(jd_data.get("ignore_unmatchable", "false"))
         if not ignore_unmatchable:
             # Raise an error if a condition can never be matched
             for yaml_dict in cls._IterDicts(jd_data):
                 for key, _value in yaml_dict.items():
-                    if ':' in key:
-                        conditions = key.split(':')[:-1]
+                    if ":" in key:
+                        conditions = key.split(":")[:-1]
 
                         for row in matrix_rows:
-                            if cls._MatchConditions(conditions, row.full_dict, branch=cls._MATCH_ANY):
+                            if cls._MatchConditions(
+                                conditions, row.full_dict, branch=cls._MATCH_ANY
+                            ):
                                 break
                         else:
                             raise UnmatchableConditionError(key)
 
         import re
+
         jobs_done_jobs = []
         for matrix_row in matrix_rows:
             jobs_done_job = JobsDoneJob()
@@ -335,34 +310,35 @@ def CreateFromYAML(cls, yaml_contents, repository):
 
             # Re-read jd_data replacing all matrix variables with their values in the current
             # matrix_row and special replacement variables 'branch' and 'name', based on repository.
-            format_dict = {
-                'branch':repository.branch,
-                'name':repository.name
-            }
+            format_dict = {"branch": repository.branch, "name": repository.name}
             format_dict.update(matrix_row.simple_dict)
             jd_formatted_data = cls._GetFormattedYAMLData(jd_data, format_dict)
             # Re-write formatted_data dict ignoring/replacing dict keys based on matrix
             for yaml_dict in cls._IterDicts(jd_formatted_data):
                 matched_conditions = {}
                 for key, option_value in list(yaml_dict.items()):
-                    if ':' not in key:
+                    if ":" not in key:
                         continue
 
-                    conditions = key.split(':')[:-1]
-                    option_name = key.split(':')[-1]
+                    conditions = key.split(":")[:-1]
+                    option_name = key.split(":")[-1]
 
                     # Remove the key with condition text
                     del yaml_dict[key]
 
                     # If the condition matches, add the new key (containing just the option_name)
-                    if not cls._MatchConditions(conditions, matrix_row.full_dict, branch=[repository.branch]):
+                    if not cls._MatchConditions(
+                        conditions, matrix_row.full_dict, branch=[repository.branch]
+                    ):
                         continue
 
-                    cls._CheckAmbiguousConditions(yaml_dict,
-                                                  matched_conditions,
-                                                  option_name,
-                                                  option_value,
-                                                  conditions)
+                    cls._CheckAmbiguousConditions(
+                        yaml_dict,
+                        matched_conditions,
+                        option_name,
+                        option_value,
+                        conditions,
+                    )
 
                     if cls._ShouldOverride(matched_conditions, option_name, conditions):
                         yaml_dict[option_name] = option_value
@@ -373,19 +349,20 @@ def CreateFromYAML(cls, yaml_contents, repository):
                 setattr(jobs_done_job, option_name, option_value)
 
             # Do not create a job if exclude=='yes'
-            if jd_formatted_data.get('exclude', 'no') == 'yes':
+            if jd_formatted_data.get("exclude", "no") == "yes":
                 continue
 
             # Do not create a job if there is no match for this branch
-            branch_patterns = jobs_done_job.branch_patterns or ['.*']
-            if not any([re.match(pattern, repository.branch) for pattern in branch_patterns]):
+            branch_patterns = jobs_done_job.branch_patterns or [".*"]
+            if not any(
+                [re.match(pattern, repository.branch) for pattern in branch_patterns]
+            ):
                 continue
 
             jobs_done_jobs.append(jobs_done_job)
 
         return jobs_done_jobs
 
-
     @classmethod
     def _GetFormattedYAMLData(cls, yaml_data, format_dict):
         if isinstance(yaml_data, str):
@@ -393,20 +370,22 @@ def _GetFormattedYAMLData(cls, yaml_data, format_dict):
         elif isinstance(yaml_data, list):
             return [cls._GetFormattedYAMLData(d, format_dict) for d in yaml_data]
         elif isinstance(yaml_data, dict):
-            return {k.format(**format_dict): cls._GetFormattedYAMLData(v, format_dict)
-                    for k, v
-                    in yaml_data.items()}
+            return {
+                k.format(**format_dict): cls._GetFormattedYAMLData(v, format_dict)
+                for k, v in yaml_data.items()
+            }
         else:
-            raise ValueError('Invalid yaml data type: {}'.format(yaml_data.__class__))
-
+            raise ValueError("Invalid yaml data type: {}".format(yaml_data.__class__))
 
     @classmethod
-    def _CheckAmbiguousConditions(cls,
-                                  yaml_dict,
-                                  previous_matched_conditions,
-                                  option_name,
-                                  option_value,
-                                  conditions):
+    def _CheckAmbiguousConditions(
+        cls,
+        yaml_dict,
+        previous_matched_conditions,
+        option_name,
+        option_value,
+        conditions,
+    ):
         if option_name not in previous_matched_conditions:
             return
         # If the value is the same we don't care if the conditions are ambiguous
@@ -415,25 +394,31 @@ def _CheckAmbiguousConditions(cls,
 
         previous_conditions = set(previous_matched_conditions[option_name])
         conditions = set(conditions)
+
         def _IsAmbiguous():
-            return not previous_conditions.issubset(conditions) and \
-                not conditions.issubset(previous_conditions)
+            return not previous_conditions.issubset(
+                conditions
+            ) and not conditions.issubset(previous_conditions)
 
         if _IsAmbiguous():
             import textwrap
+
             raise ValueError(
-                textwrap.dedent('''\
+                textwrap.dedent(
+                    """\
                 Option {option_name} has ambiguous conditions and would be overridden
                 Old value: {old_value}
                 New value: {new_value}
                 Old conditions: {old_conditions}
-                New conditions: {new_conditions}''')
-                .format(option_name=option_name,
-                        old_value=yaml_dict[option_name],
-                        new_value=option_value,
-                        old_conditions=previous_conditions,
-                        new_conditions=conditions))
-
+                New conditions: {new_conditions}"""
+                ).format(
+                    option_name=option_name,
+                    old_value=yaml_dict[option_name],
+                    new_value=option_value,
+                    old_conditions=previous_conditions,
+                    new_conditions=conditions,
+                )
+            )
 
     @classmethod
     def _ShouldOverride(cls, previous_matched_conditions, option_name, conditions):
@@ -444,25 +429,24 @@ def _ShouldOverride(cls, previous_matched_conditions, option_name, conditions):
         conditions = set(conditions)
         return conditions.issuperset(previous_conditions)
 
-
     @classmethod
     def CreateFromFile(cls, filename, repository):
-        '''
+        """
         :param unicode filename:
             Path to a jobs_done file
 
         :param repository:
             .. seealso:: CreateFromYAML
-        '''
-        with io.open(filename, encoding='utf-8') as f:
+        """
+        with io.open(filename, encoding="utf-8") as f:
             contents = f.read()
         return cls.CreateFromYAML(contents, repository)
 
-
     _MATCH_ANY = object()
+
     @classmethod
     def _MatchConditions(cls, conditions, *fact_dicts, **extra_facts):
-        '''
+        """
         Check if the given conditions matches a set of facts.
 
         e.g.:
@@ -493,8 +477,9 @@ def _MatchConditions(cls, conditions, *fact_dicts, **extra_facts):
 
         :return boolean:
             Returns True if all the given conditions matches the given facts.
-        '''
+        """
         import re
+
         # Assemble facts
         facts = {}
         for fact_dict in fact_dicts:
@@ -502,15 +487,16 @@ def _MatchConditions(cls, conditions, *fact_dicts, **extra_facts):
         facts.update(extra_facts)
 
         def _Match(condition):
-            variable_name, match_mask = condition.split('-', 1)
+            variable_name, match_mask = condition.split("-", 1)
             fact_values = facts[variable_name]
-            return fact_values is cls._MATCH_ANY or any(re.match(match_mask, fact) for fact in fact_values)
+            return fact_values is cls._MATCH_ANY or any(
+                re.match(match_mask, fact) for fact in fact_values
+            )
 
         return all(map(_Match, conditions))
 
-
     class _MatrixRow(object):
-        '''
+        """
         Holds a combination of matrix values.
 
         :ivar dict(unicode,list(unicode)) full_dict:
@@ -519,10 +505,10 @@ class _MatrixRow(object):
 
         :ivar dict(unicode,unicode) simple_dict:
             Maps names to the main value. .. seealso:: `full_dict`
-        '''
+        """
 
         def __init__(self, names, values):
-            '''
+            """
             Create a matrix-row instance from a matrix-dict and a value tuple.
 
             :param list(unicode) names:
@@ -531,22 +517,21 @@ def __init__(self, names, values):
             :param list(unicode) values:
                 List of values assumed by this row.
                 One value for each name in names parameter.
-            '''
-            values = tuple(i.split(',') for i in values)
+            """
+            values = tuple(i.split(",") for i in values)
             self.full_dict = dict(zip(names, values))
             self.simple_dict = dict((i, j[0]) for (i, j) in self.full_dict.items())
 
-
         @classmethod
         def CreateFromDict(self, matrix_dict):
-            '''
+            """
             Write up all matrix_rows from possible combinations of the job matrix
 
             Inspired on http://stackoverflow.com/a/3873734/1209622
 
             :param dict(unicode:tuple) matrix_dict:
                 A dictionary mapping names to values.
-            '''
+            """
             import itertools as it
 
             # Create all combinations of values available in the matrix
@@ -554,10 +539,9 @@ def CreateFromDict(self, matrix_dict):
             value_combinations = it.product(*list(matrix_dict.values()))
             return [JobsDoneJob._MatrixRow(names, v) for v in value_combinations]
 
-
     @classmethod
     def _IterDicts(cls, obj):
-        '''
+        """
         Iterates over all dicts in an object.
 
         Works recursively for lists and dicts.
@@ -566,7 +550,7 @@ def _IterDicts(cls, obj):
 
         :yield dict:
             Dicts found by iterating over `obj`
-        '''
+        """
         if isinstance(obj, dict):
             yield obj
 
@@ -580,31 +564,34 @@ def _IterDicts(cls, obj):
                     yield x
 
 
-#===================================================================================================
+# ===================================================================================================
 # UnknownJobsDoneJobOption
-#===================================================================================================
+# ===================================================================================================
 class UnknownJobsDoneFileOption(RuntimeError):
-    '''
+    """
     Raised when parsing an unknown option.
 
     :ivar unicode option_name:
         Name of the unknown option.
-    '''
+    """
+
     def __init__(self, option_name):
         self.option_name = option_name
         RuntimeError.__init__(
             self,
-            'Received unknown option "%s".\n\nAvailable options are:\n%s' % \
-            (str(option_name), '\n'.join('- ' + str(o) for o in sorted(JobsDoneJob.PARSEABLE_OPTIONS)))
+            'Received unknown option "%s".\n\nAvailable options are:\n%s'
+            % (
+                str(option_name),
+                "\n".join("- " + str(o) for o in sorted(JobsDoneJob.PARSEABLE_OPTIONS)),
+            ),
         )
 
 
-
-#===================================================================================================
+# ===================================================================================================
 # JobsDoneFileTypeError
-#===================================================================================================
+# ===================================================================================================
 class JobsDoneFileTypeError(TypeError):
-    '''
+    """
     Raised when parsing an option with a bad type.
 
     :ivar unicode option_name:
@@ -620,7 +607,8 @@ class JobsDoneFileTypeError(TypeError):
         e.g.
             (list,)
             (dict, unicode)
-    '''
+    """
+
     def __init__(self, option_name, obtained_type, accepted_types, option_value):
         self.option_name = option_name
         self.obtained_type = obtained_type
@@ -629,50 +617,51 @@ def __init__(self, option_name, obtained_type, accepted_types, option_value):
 
         TypeError.__init__(
             self,
-            'On option "%s". Expected one of "%s" but got "%s". Value:\n%r' % \
-            (option_name, accepted_types, obtained_type, option_value)
+            'On option "%s". Expected one of "%s" but got "%s". Value:\n%r'
+            % (option_name, accepted_types, obtained_type, option_value),
         )
 
 
-
-#===================================================================================================
+# ===================================================================================================
 # UnmatchableConditionError
-#===================================================================================================
+# ===================================================================================================
 class UnmatchableConditionError(ValueError):
-    '''
+    """
     Raised when declaring a condition that can never be matches based on available matrix rows.
 
     :ivar unicode option:
         Option with a condition that can't be matched.
         e.g.:
             'planet-pluto:junit_patterns'
-    '''
+    """
+
     def __init__(self, option):
         self.option = option
 
         ValueError.__init__(
             self,
-            'Condition "%s" can never be matched based on possible matrix rows.' % option
+            'Condition "%s" can never be matched based on possible matrix rows.'
+            % option,
         )
 
 
-_TRUE_VALUES = ['TRUE', 'YES', '1']
-_FALSE_VALUES = ['FALSE', 'NO', '0']
+_TRUE_VALUES = ["TRUE", "YES", "1"]
+_FALSE_VALUES = ["FALSE", "NO", "0"]
 _TRUE_FALSE_VALUES = _TRUE_VALUES + _FALSE_VALUES
 _KNOWN_NUMBER_TYPES = None
 
-#===================================================================================================
+# ===================================================================================================
 # Boolean
-#===================================================================================================
+# ===================================================================================================
 def Boolean(text):
-    '''
+    """
     :param unicode text:
         A text semantically representing a boolean value.
 
     :rtype: bool
     :returns:
         Returns a boolean represented by the given text.
-    '''
+    """
     text_upper = text.upper()
     if text_upper not in _TRUE_FALSE_VALUES:
         raise ValueError(
diff --git a/src/jobs_done10/repository.py b/src/jobs_done10/repository.py
index fe0b035..d78cf87 100644
--- a/src/jobs_done10/repository.py
+++ b/src/jobs_done10/repository.py
@@ -1,11 +1,8 @@
-
-
-
-#===================================================================================================
+# ===================================================================================================
 #  Repository
-#===================================================================================================
+# ===================================================================================================
 class Repository(object):
-    '''
+    """
     Represents a source control repository used in a continuous integration job.
 
     :cvar unicode url:
@@ -20,21 +17,20 @@ class Repository(object):
         e.g.
             url = 'https://server/repo.git'
             name = 'repo'
-    '''
+    """
 
-    def __init__(self, url=None, branch='master'):
+    def __init__(self, url=None, branch="master"):
         self.url = url
         self.branch = branch
 
     @property
     def name(self):
         import re
-        return re.match(r'.*/([^\./]+)(\.git/?)?$', self.url).groups()[0]
 
+        return re.match(r".*/([^\./]+)(\.git/?)?$", self.url).groups()[0]
 
     def __eq__(self, other):
         return self.url == other.url and self.branch == other.branch
 
-
     def __ne__(self, other):
         return not self == other
diff --git a/src/jobs_done10/server/app.py b/src/jobs_done10/server/app.py
index 719ac73..dac0e82 100644
--- a/src/jobs_done10/server/app.py
+++ b/src/jobs_done10/server/app.py
@@ -7,20 +7,22 @@
 from dotenv import load_dotenv
 from pygments import highlight
 from pygments.formatters import HtmlFormatter
-from pygments.lexers import JsonLexer, PythonTracebackLexer
+from pygments.lexers import JsonLexer
+from pygments.lexers import PythonTracebackLexer
 
-from jobs_done10.server_email_templates import EMAIL_HTML, EMAIL_PLAINTEXT
 from jobs_done10 import get_version_title
+from jobs_done10.server_email_templates import EMAIL_HTML
+from jobs_done10.server_email_templates import EMAIL_PLAINTEXT
 
 
-app = flask.Flask('jobs_done')
-load_dotenv(dotenv_path=os.environ.get('JOBSDONE_DOTENV'))
+app = flask.Flask("jobs_done")
+load_dotenv(dotenv_path=os.environ.get("JOBSDONE_DOTENV"))
 if not app.debug:
-    app.logger.setLevel('INFO')
-app.logger.info(f'Initializing Server App - {get_version_title()}')
+    app.logger.setLevel("INFO")
+app.logger.info(f"Initializing Server App - {get_version_title()}")
 
 
-@app.route('/', methods=['GET', 'POST'])
+@app.route("/", methods=["GET", "POST"])
 def index():
     """
     Jenkins job generation end-point
@@ -39,7 +41,7 @@ def index():
          "toHash": "8522b06a7c330008814a522d0342be9a997a1460", "type": "UPDATE"}]}
     """
     payload = flask.request.json
-    if flask.request.method == 'GET' or payload is None or payload.get('test'):
+    if flask.request.method == "GET" or payload is None or payload.get("test"):
         # return a 200 response also on POST, when no JSON data is posted; this is useful
         # because the "Test Connection" in BitBucket does just that, making it easy to verify
         # we have the correct version up.
@@ -58,22 +60,22 @@ def _process_jobs_done_request(payload) -> str:
     """
     Generate/update a Jenkins job from a request and returns a debug message
     """
-    if not isinstance(payload, dict) or 'eventKey' not in payload:
-        raise RuntimeError(f'Invalid request json data: {pprint.pformat(payload)}')
+    if not isinstance(payload, dict) or "eventKey" not in payload:
+        raise RuntimeError(f"Invalid request json data: {pprint.pformat(payload)}")
 
-    app.logger.info(f'Received request:\n{pprint.pformat(payload)}')
+    app.logger.info(f"Received request:\n{pprint.pformat(payload)}")
 
-    stash_url = os.environ['JD_STASH_URL'].rstrip()
-    stash_username = os.environ['JD_STASH_USERNAME']
-    stash_password = os.environ['JD_STASH_PASSWORD']
-    project_key = payload['repository']['project']['key']
-    slug = payload['repository']['slug']
+    stash_url = os.environ["JD_STASH_URL"].rstrip()
+    stash_username = os.environ["JD_STASH_USERNAME"]
+    stash_password = os.environ["JD_STASH_PASSWORD"]
+    project_key = payload["repository"]["project"]["key"]
+    slug = payload["repository"]["slug"]
 
     all_new_jobs = []
     all_updated_jobs = []
     all_deleted_jobs = []
     lines = []
-    for change in payload['changes']:
+    for change in payload["changes"]:
         try:
             jobs_done_file_contents = get_file_contents(
                 stash_url=stash_url,
@@ -81,8 +83,8 @@ def _process_jobs_done_request(payload) -> str:
                 password=stash_password,
                 project_key=project_key,
                 slug=slug,
-                path='.jobs_done.yaml',
-                ref=change['toHash'],
+                path=".jobs_done.yaml",
+                ref=change["toHash"],
             )
         except IOError:
             jobs_done_file_contents = None
@@ -90,19 +92,24 @@ def _process_jobs_done_request(payload) -> str:
         from jobs_done10.generators import jenkins
         from jobs_done10.repository import Repository
 
-        clone_url = get_clone_url(stash_url=stash_url, username=stash_username, password=stash_password,
-                                  project_key=project_key, slug=slug)
+        clone_url = get_clone_url(
+            stash_url=stash_url,
+            username=stash_username,
+            password=stash_password,
+            project_key=project_key,
+            slug=slug,
+        )
 
-        branch = change['ref']['id']
-        prefix = 'refs/heads/'
+        branch = change["ref"]["id"]
+        prefix = "refs/heads/"
         if not branch.startswith(prefix):
-            lines.append(f'WARNING: ignoring branch {branch}: expected {prefix}')
+            lines.append(f"WARNING: ignoring branch {branch}: expected {prefix}")
             continue
-        branch = branch[len(prefix):]
+        branch = branch[len(prefix) :]
         repository = Repository(url=clone_url, branch=branch)
-        jenkins_url = os.environ['JD_JENKINS_URL'].rstrip('/')
-        jenkins_username = os.environ['JD_JENKINS_USERNAME']
-        jenkins_password = os.environ['JD_JENKINS_PASSWORD']
+        jenkins_url = os.environ["JD_JENKINS_URL"].rstrip("/")
+        jenkins_username = os.environ["JD_JENKINS_USERNAME"]
+        jenkins_password = os.environ["JD_JENKINS_PASSWORD"]
 
         new_jobs, updated_jobs, deleted_jobs = jenkins.UploadJobsFromFile(
             repository=repository,
@@ -115,11 +122,11 @@ def _process_jobs_done_request(payload) -> str:
         all_updated_jobs.extend(updated_jobs)
         all_deleted_jobs.extend(deleted_jobs)
 
-    lines.extend(f'NEW - {x}' for x in all_new_jobs)
-    lines.extend(f'UPD - {x}' for x in all_updated_jobs)
-    lines.extend(f'DEL - {x}' for x in all_deleted_jobs)
+    lines.extend(f"NEW - {x}" for x in all_new_jobs)
+    lines.extend(f"UPD - {x}" for x in all_updated_jobs)
+    lines.extend(f"DEL - {x}" for x in all_deleted_jobs)
 
-    message = '\n'.join(lines)
+    message = "\n".join(lines)
     app.logger.info(message)
     return message
 
@@ -131,37 +138,45 @@ def _process_jobs_done_error(payload) -> str:
     """
     error_traceback = traceback.format_exc()
     lines = [
-        f'ERROR processing request: {flask.request}',
-        '',
-        'JSON data:',
-        '',
+        f"ERROR processing request: {flask.request}",
+        "",
+        "JSON data:",
+        "",
         pprint.pformat(payload),
-        '',
-        '',
+        "",
+        "",
         error_traceback,
-        '',
+        "",
     ]
     try:
         recipient = send_email_with_error(payload, error_traceback)
     except Exception:
-        lines.append('*' * 80)
-        lines.append('ERROR SENDING EMAIL:')
+        lines.append("*" * 80)
+        lines.append("ERROR SENDING EMAIL:")
         lines.append(traceback.format_exc())
     else:
-        lines.append(f'Email sent to {recipient}')
-    message = '\n'.join(lines)
-    app.logger.exception('Uncaught exception')
+        lines.append(f"Email sent to {recipient}")
+    message = "\n".join(lines)
+    app.logger.exception("Uncaught exception")
     return message
 
 
-def get_file_contents(*, stash_url: str, username: str, password: str, project_key: str, slug: str, path: str,
-                      ref: str) -> str:
+def get_file_contents(
+    *,
+    stash_url: str,
+    username: str,
+    password: str,
+    project_key: str,
+    slug: str,
+    path: str,
+    ref: str,
+) -> str:
     """
     Get the file contents from the stash server.
 
     We are using a "raw" Get which returns the entire file contents as text.
     """
-    file_url = stash_url + f'/projects/{project_key}/repos/{slug}/raw/{path}?at={ref}'
+    file_url = stash_url + f"/projects/{project_key}/repos/{slug}/raw/{path}?at={ref}"
     response = requests.get(file_url, auth=(username, password))
 
     if response.status_code == 404:
@@ -174,7 +189,9 @@ def get_file_contents(*, stash_url: str, username: str, password: str, project_k
     return contents
 
 
-def get_clone_url(*, stash_url: str, username: str, password: str, project_key: str, slug: str) -> str:
+def get_clone_url(
+    *, stash_url: str, username: str, password: str, project_key: str, slug: str
+) -> str:
     """
     Get information about the repository, returning the SSH clone url.
 
@@ -199,19 +216,21 @@ def get_clone_url(*, stash_url: str, username: str, password: str, project_key:
      'state': 'AVAILABLE',
      'statusMessage': 'Available'}
     """
-    url = f'{stash_url}/rest/api/1.0/projects/{project_key}/repos/{slug}'
+    url = f"{stash_url}/rest/api/1.0/projects/{project_key}/repos/{slug}"
     response = requests.get(url, auth=(username, password))
     if response.status_code != 200:
         response.raise_for_status()
 
     data = response.json()
-    for clone_url in data['links']['clone']:
-        if clone_url['name'] == 'ssh':
-            return clone_url['href']
+    for clone_url in data["links"]["clone"]:
+        if clone_url["name"] == "ssh":
+            return clone_url["href"]
 
     import pprint
 
-    raise RuntimeError(f'Could not find the ssh clone url in json response:\n{pprint.pformat(data)}')
+    raise RuntimeError(
+        f"Could not find the ssh clone url in json response:\n{pprint.pformat(data)}"
+    )
 
 
 def send_email_with_error(data: dict, error_traceback: str) -> str:
@@ -224,39 +243,48 @@ def send_email_with_error(data: dict, error_traceback: str) -> str:
     """
     import mailer
 
-    recipient = data['actor']['emailAddress']
+    recipient = data["actor"]["emailAddress"]
 
-    project_key = data['repository']['project']['key']
-    slug = data['repository']['slug']
-    changes = [(change['ref']['id'], change['toHash']) for change in data['changes']]
-    changes_msg = ', '.join(f'{branch.replace("refs/heads/", "")} @ {commit[:7]}' for (branch, commit) in changes)
-    subject = f'JobsDone failure during push to {project_key}/{slug} ({changes_msg})'
+    project_key = data["repository"]["project"]["key"]
+    slug = data["repository"]["slug"]
+    changes = [(change["ref"]["id"], change["toHash"]) for change in data["changes"]]
+    changes_msg = ", ".join(
+        f'{branch.replace("refs/heads/", "")} @ {commit[:7]}'
+        for (branch, commit) in changes
+    )
+    subject = f"JobsDone failure during push to {project_key}/{slug} ({changes_msg})"
 
     message = mailer.Message(
-        From=os.environ['JD_EMAIL_FROM'],
+        From=os.environ["JD_EMAIL_FROM"],
         To=[recipient],
         # RTo=None,
         # Cc=self.cc,
         Subject=subject,
-        charset='UTF-8',
+        charset="UTF-8",
     )
 
     pretty_json = pprint.pformat(data)
-    message.Body = EMAIL_PLAINTEXT.format(error_traceback=error_traceback, pretty_json=pretty_json)
-    style = 'colorful'
+    message.Body = EMAIL_PLAINTEXT.format(
+        error_traceback=error_traceback, pretty_json=pretty_json
+    )
+    style = "colorful"
     html = EMAIL_HTML.format(
-        error_traceback_html=highlight(error_traceback, PythonTracebackLexer(), HtmlFormatter(style=style)),
-        pretty_json_html=highlight(pretty_json, JsonLexer(), HtmlFormatter(style=style)),
+        error_traceback_html=highlight(
+            error_traceback, PythonTracebackLexer(), HtmlFormatter(style=style)
+        ),
+        pretty_json_html=highlight(
+            pretty_json, JsonLexer(), HtmlFormatter(style=style)
+        ),
     )
 
     message.Html = html
 
     sender = mailer.Mailer(
-        host=os.environ['JD_EMAIL_SERVER'],
-        port=int(os.environ['JD_EMAIL_PORT']),
+        host=os.environ["JD_EMAIL_SERVER"],
+        port=int(os.environ["JD_EMAIL_PORT"]),
         use_tls=True,
-        usr=os.environ['JD_EMAIL_USER'],
-        pwd=os.environ['JD_EMAIL_PASSWORD'],
+        usr=os.environ["JD_EMAIL_USER"],
+        pwd=os.environ["JD_EMAIL_PASSWORD"],
     )
     sender.send(message)
     return recipient
diff --git a/src/jobs_done10/server_email_templates.py b/src/jobs_done10/server_email_templates.py
index e369b36..8784129 100644
--- a/src/jobs_done10/server_email_templates.py
+++ b/src/jobs_done10/server_email_templates.py
@@ -22,81 +22,81 @@
   <style type="text/css" media="screen">
  .highlight .hll {{ background-color: #f1fa8c }}
  .highlight  {{ background: #282a36; color: #f8f8f2 }}
- .highlight .c {{ color: #6272a4 }} 
- .highlight .err {{ color: #f8f8f2 }} 
- .highlight .g {{ color: #f8f8f2 }} 
- .highlight .k {{ color: #ff79c6 }} 
- .highlight .l {{ color: #f8f8f2 }} 
- .highlight .n {{ color: #f8f8f2 }} 
- .highlight .o {{ color: #ff79c6 }} 
- .highlight .x {{ color: #f8f8f2 }} 
- .highlight .p {{ color: #f8f8f2 }} 
- .highlight .ch {{ color: #6272a4 }} 
- .highlight .cm {{ color: #6272a4 }} 
- .highlight .cp {{ color: #ff79c6 }} 
- .highlight .cpf {{ color: #6272a4 }} 
- .highlight .c1 {{ color: #6272a4 }} 
- .highlight .cs {{ color: #6272a4 }} 
- .highlight .gd {{ color: #8b080b }} 
- .highlight .ge {{ color: #f8f8f2; text-decoration: underline }} 
- .highlight .gr {{ color: #f8f8f2 }} 
- .highlight .gh {{ color: #f8f8f2; font-weight: bold }} 
- .highlight .gi {{ color: #f8f8f2; font-weight: bold }} 
- .highlight .go {{ color: #44475a }} 
- .highlight .gp {{ color: #f8f8f2 }} 
- .highlight .gs {{ color: #f8f8f2 }} 
- .highlight .gu {{ color: #f8f8f2; font-weight: bold }} 
- .highlight .gt {{ color: #f8f8f2 }} 
- .highlight .kc {{ color: #ff79c6 }} 
- .highlight .kd {{ color: #8be9fd; font-style: italic }} 
- .highlight .kn {{ color: #ff79c6 }} 
- .highlight .kp {{ color: #ff79c6 }} 
- .highlight .kr {{ color: #ff79c6 }} 
- .highlight .kt {{ color: #8be9fd }} 
- .highlight .ld {{ color: #f8f8f2 }} 
- .highlight .m {{ color: #bd93f9 }} 
- .highlight .s {{ color: #f1fa8c }} 
- .highlight .na {{ color: #50fa7b }} 
- .highlight .nb {{ color: #8be9fd; font-style: italic }} 
- .highlight .nc {{ color: #50fa7b }} 
- .highlight .no {{ color: #f8f8f2 }} 
- .highlight .nd {{ color: #f8f8f2 }} 
- .highlight .ni {{ color: #f8f8f2 }} 
- .highlight .ne {{ color: #f8f8f2 }} 
- .highlight .nf {{ color: #50fa7b }} 
- .highlight .nl {{ color: #8be9fd; font-style: italic }} 
- .highlight .nn {{ color: #f8f8f2 }} 
- .highlight .nx {{ color: #f8f8f2 }} 
- .highlight .py {{ color: #f8f8f2 }} 
- .highlight .nt {{ color: #ff79c6 }} 
- .highlight .nv {{ color: #8be9fd; font-style: italic }} 
- .highlight .ow {{ color: #ff79c6 }} 
- .highlight .w {{ color: #f8f8f2 }} 
- .highlight .mb {{ color: #bd93f9 }} 
- .highlight .mf {{ color: #bd93f9 }} 
- .highlight .mh {{ color: #bd93f9 }} 
- .highlight .mi {{ color: #bd93f9 }} 
- .highlight .mo {{ color: #bd93f9 }} 
- .highlight .sa {{ color: #f1fa8c }} 
- .highlight .sb {{ color: #f1fa8c }} 
- .highlight .sc {{ color: #f1fa8c }} 
- .highlight .dl {{ color: #f1fa8c }} 
- .highlight .sd {{ color: #f1fa8c }} 
- .highlight .s2 {{ color: #f1fa8c }} 
- .highlight .se {{ color: #f1fa8c }} 
- .highlight .sh {{ color: #f1fa8c }} 
- .highlight .si {{ color: #f1fa8c }} 
- .highlight .sx {{ color: #f1fa8c }} 
- .highlight .sr {{ color: #f1fa8c }} 
- .highlight .s1 {{ color: #f1fa8c }} 
- .highlight .ss {{ color: #f1fa8c }} 
- .highlight .bp {{ color: #f8f8f2; font-style: italic }} 
- .highlight .fm {{ color: #50fa7b }} 
- .highlight .vc {{ color: #8be9fd; font-style: italic }} 
- .highlight .vg {{ color: #8be9fd; font-style: italic }} 
- .highlight .vi {{ color: #8be9fd; font-style: italic }} 
- .highlight .vm {{ color: #8be9fd; font-style: italic }} 
- .highlight .il {{ color: #bd93f9 }} 
+ .highlight .c {{ color: #6272a4 }}
+ .highlight .err {{ color: #f8f8f2 }}
+ .highlight .g {{ color: #f8f8f2 }}
+ .highlight .k {{ color: #ff79c6 }}
+ .highlight .l {{ color: #f8f8f2 }}
+ .highlight .n {{ color: #f8f8f2 }}
+ .highlight .o {{ color: #ff79c6 }}
+ .highlight .x {{ color: #f8f8f2 }}
+ .highlight .p {{ color: #f8f8f2 }}
+ .highlight .ch {{ color: #6272a4 }}
+ .highlight .cm {{ color: #6272a4 }}
+ .highlight .cp {{ color: #ff79c6 }}
+ .highlight .cpf {{ color: #6272a4 }}
+ .highlight .c1 {{ color: #6272a4 }}
+ .highlight .cs {{ color: #6272a4 }}
+ .highlight .gd {{ color: #8b080b }}
+ .highlight .ge {{ color: #f8f8f2; text-decoration: underline }}
+ .highlight .gr {{ color: #f8f8f2 }}
+ .highlight .gh {{ color: #f8f8f2; font-weight: bold }}
+ .highlight .gi {{ color: #f8f8f2; font-weight: bold }}
+ .highlight .go {{ color: #44475a }}
+ .highlight .gp {{ color: #f8f8f2 }}
+ .highlight .gs {{ color: #f8f8f2 }}
+ .highlight .gu {{ color: #f8f8f2; font-weight: bold }}
+ .highlight .gt {{ color: #f8f8f2 }}
+ .highlight .kc {{ color: #ff79c6 }}
+ .highlight .kd {{ color: #8be9fd; font-style: italic }}
+ .highlight .kn {{ color: #ff79c6 }}
+ .highlight .kp {{ color: #ff79c6 }}
+ .highlight .kr {{ color: #ff79c6 }}
+ .highlight .kt {{ color: #8be9fd }}
+ .highlight .ld {{ color: #f8f8f2 }}
+ .highlight .m {{ color: #bd93f9 }}
+ .highlight .s {{ color: #f1fa8c }}
+ .highlight .na {{ color: #50fa7b }}
+ .highlight .nb {{ color: #8be9fd; font-style: italic }}
+ .highlight .nc {{ color: #50fa7b }}
+ .highlight .no {{ color: #f8f8f2 }}
+ .highlight .nd {{ color: #f8f8f2 }}
+ .highlight .ni {{ color: #f8f8f2 }}
+ .highlight .ne {{ color: #f8f8f2 }}
+ .highlight .nf {{ color: #50fa7b }}
+ .highlight .nl {{ color: #8be9fd; font-style: italic }}
+ .highlight .nn {{ color: #f8f8f2 }}
+ .highlight .nx {{ color: #f8f8f2 }}
+ .highlight .py {{ color: #f8f8f2 }}
+ .highlight .nt {{ color: #ff79c6 }}
+ .highlight .nv {{ color: #8be9fd; font-style: italic }}
+ .highlight .ow {{ color: #ff79c6 }}
+ .highlight .w {{ color: #f8f8f2 }}
+ .highlight .mb {{ color: #bd93f9 }}
+ .highlight .mf {{ color: #bd93f9 }}
+ .highlight .mh {{ color: #bd93f9 }}
+ .highlight .mi {{ color: #bd93f9 }}
+ .highlight .mo {{ color: #bd93f9 }}
+ .highlight .sa {{ color: #f1fa8c }}
+ .highlight .sb {{ color: #f1fa8c }}
+ .highlight .sc {{ color: #f1fa8c }}
+ .highlight .dl {{ color: #f1fa8c }}
+ .highlight .sd {{ color: #f1fa8c }}
+ .highlight .s2 {{ color: #f1fa8c }}
+ .highlight .se {{ color: #f1fa8c }}
+ .highlight .sh {{ color: #f1fa8c }}
+ .highlight .si {{ color: #f1fa8c }}
+ .highlight .sx {{ color: #f1fa8c }}
+ .highlight .sr {{ color: #f1fa8c }}
+ .highlight .s1 {{ color: #f1fa8c }}
+ .highlight .ss {{ color: #f1fa8c }}
+ .highlight .bp {{ color: #f8f8f2; font-style: italic }}
+ .highlight .fm {{ color: #50fa7b }}
+ .highlight .vc {{ color: #8be9fd; font-style: italic }}
+ .highlight .vg {{ color: #8be9fd; font-style: italic }}
+ .highlight .vi {{ color: #8be9fd; font-style: italic }}
+ .highlight .vm {{ color: #8be9fd; font-style: italic }}
+ .highlight .il {{ color: #bd93f9 }}
   </style>
 </head>
 
diff --git a/src/jobs_done10/xml_factory/__init__.py b/src/jobs_done10/xml_factory/__init__.py
index 45eac4d..39dd965 100644
--- a/src/jobs_done10/xml_factory/__init__.py
+++ b/src/jobs_done10/xml_factory/__init__.py
@@ -1,5 +1,3 @@
-
-
-from ._pretty_xml import WritePrettyXML, WritePrettyXMLElement
+from ._pretty_xml import WritePrettyXML
+from ._pretty_xml import WritePrettyXMLElement
 from ._xml_factory import XmlFactory
-
diff --git a/src/jobs_done10/xml_factory/_pretty_xml.py b/src/jobs_done10/xml_factory/_pretty_xml.py
index 0774a7b..57e8e69 100644
--- a/src/jobs_done10/xml_factory/_pretty_xml.py
+++ b/src/jobs_done10/xml_factory/_pretty_xml.py
@@ -1,13 +1,11 @@
-
-
 from xml.etree import ElementTree
 
 
-#===================================================================================================
+# ===================================================================================================
 # WritePrettyXML
-#===================================================================================================
+# ===================================================================================================
 def WritePrettyXML(input, output):
-    '''
+    """
     Writes the input file in pretty xml.
 
     :type input: unicode or file
@@ -17,9 +15,9 @@ def WritePrettyXML(input, output):
     :type output: unicode or file
     :param output:
         The output filename or file opened for writing.
-    '''
+    """
     if isinstance(output, str):
-        out_stream = file(output, 'w')
+        out_stream = file(output, "w")
         close_output = True
     else:
         out_stream = output
@@ -32,12 +30,11 @@ def WritePrettyXML(input, output):
             out_stream.close()
 
 
-
-#===================================================================================================
+# ===================================================================================================
 # WritePrettyXMLElement
-#===================================================================================================
+# ===================================================================================================
 def WritePrettyXMLElement(oss, element, indent=0):
-    '''
+    """
     Writes an xml element in the given file (oss) recursivelly, in pretty xml.
 
     :param file oss:
@@ -49,34 +46,33 @@ def WritePrettyXMLElement(oss, element, indent=0):
     :param int indent:
         The level of indentation to write the tag.
         This is used internally for pretty printing.
-    '''
+    """
     from xml.sax.saxutils import escape
 
-    INDENT = '  '
+    INDENT = "  "
 
     # Start tag
-    oss.write(INDENT * indent + '<%s' % element.tag)
+    oss.write(INDENT * indent + "<%s" % element.tag)
     for i_name, i_value in sorted(element.attrib.items()):
         oss.write(' %s="%s"' % (i_name, escape(i_value)))
 
     if len(element) == 0 and element.text is None:
-        oss.write('/>')
+        oss.write("/>")
         return
 
-    oss.write('>')
+    oss.write(">")
 
     # Sub-elements
     for i_element in element:
-        oss.write('\n')
+        oss.write("\n")
         WritePrettyXMLElement(oss, i_element, indent + 1)
 
     # Text
     if element.text is not None:
         # "&#xd;" is the hexadecimal xml entity for "\r".
-        oss.write(escape(element.text, {'\r': '&#xd;'}))
+        oss.write(escape(element.text, {"\r": "&#xd;"}))
 
     # End tag
     if element.text is None:
-        oss.write('\n' + INDENT * indent)
-    oss.write('</%s>' % element.tag)
-
+        oss.write("\n" + INDENT * indent)
+    oss.write("</%s>" % element.tag)
diff --git a/src/jobs_done10/xml_factory/_xml_factory.py b/src/jobs_done10/xml_factory/_xml_factory.py
index a26fac0..70ef2a0 100644
--- a/src/jobs_done10/xml_factory/_xml_factory.py
+++ b/src/jobs_done10/xml_factory/_xml_factory.py
@@ -1,5 +1,3 @@
-
-
 from io import StringIO
 from itertools import chain
 from xml.etree import ElementTree
@@ -7,11 +5,11 @@
 from ._pretty_xml import WritePrettyXMLElement
 
 
-#===================================================================================================
+# ===================================================================================================
 # XmlFactory
-#===================================================================================================
+# ===================================================================================================
 class XmlFactory(object):
-    '''
+    """
     Fast and easy XML creation class.
 
     This class provides a simple a fast way of creating XML files in Python. It tries to deduce as
@@ -25,23 +23,24 @@ class XmlFactory(object):
         xml['alpha/delta'] = 'XXX' # Create delta tag with text
 
         xml.Write('filename.xml') # Always write with a pretty XML format
-    '''
+    """
 
     def __init__(self, root_element):
-        '''
+        """
         :type root_element: str | Element
         :param root_element:
-        '''
+        """
         if isinstance(root_element, str):
             self.root = ElementTree.Element(root_element)
         elif isinstance(root_element, ElementTree.Element):
             self.root = root_element
         else:
-            raise TypeError("Unknown root_element parameter type: %s" % type(root_element))
-
+            raise TypeError(
+                "Unknown root_element parameter type: %s" % type(root_element)
+            )
 
     def __setitem__(self, name, value):
-        '''
+        """
         Create a new element or attribute:
 
         :param unicode name:
@@ -57,9 +56,9 @@ def __setitem__(self, name, value):
         @examples:
             xml['alpha/bravo'] = 'XXX' # Create bravo tag with 'XXX' as text contents
             xml['alpha.class'] = 'CLS' # Create alpha with the attribute class='CLS'
-        '''
-        if '@' in name:
-            element_name, attr_name = name.rsplit('@')
+        """
+        if "@" in name:
+            element_name, attr_name = name.rsplit("@")
             result = self._ObtainElement(element_name)
             result.attrib[attr_name] = str(value)
         else:
@@ -67,9 +66,8 @@ def __setitem__(self, name, value):
             result.text = str(value)
         return XmlFactory(result)
 
-
     def __getitem__(self, name):
-        '''
+        """
         Create and returns xml element.
 
         :param unicode name:
@@ -78,29 +76,28 @@ def __getitem__(self, name):
         :rtype: Element
         :returns:
             Returns the element created.
-        '''
-        assert '@' not in name, 'The "at" (@) is used for attribute definitions'
+        """
+        assert "@" not in name, 'The "at" (@) is used for attribute definitions'
         result = self._ObtainElement(name)
         return XmlFactory(result)
 
-
     def _ObtainElement(self, name):
-        '''
+        """
         Create and returns a xml element with the given name.
 
         :param unicode name:
             A XML path including. Each sub-client tag separated by a slash.
             If any of the parts ends with a "+" it creates a new sub-element in that part even if
             it already exists.
-        '''
+        """
         parent = self.root
-        if name == '':
+        if name == "":
             # On Python 2.7 parent.find('') returns None instead of the parent itself
             result = parent
         else:
-            parts = name.split('/')
+            parts = name.split("/")
             for i_part in parts:
-                if i_part.endswith('+'):
+                if i_part.endswith("+"):
                     i_part = i_part[:-1]
                     result = ElementTree.SubElement(parent, i_part)
                 else:
@@ -110,49 +107,47 @@ def _ObtainElement(self, name):
                 parent = result
         return result
 
-
     def Print(self, oss=None, xml_header=False):
-        '''
+        """
         Prints the resulting XML in the stdout or the given output stream.
 
         :type oss: file-like object | None
         :param oss:
             A file-like object where to write the XML output. If None, writes the output in the
             stdout.
-        '''
+        """
 
         if oss is None:
             import sys
+
             oss = sys.stdout
 
         if xml_header:
             oss.write('<?xml version="1.0" ?>\n')
         WritePrettyXMLElement(oss, self.root)
 
-
     def GetContents(self, xml_header=False):
-        '''
+        """
         Returns the resulting XML.
 
         :return unicode:
-        '''
+        """
         oss = StringIO()
         self.Print(oss, xml_header=xml_header)
         return oss.getvalue()
 
-
     def AsDict(self):
-        '''
+        """
         Returns the data-structure as dict.
 
         :return dict:
-        '''
+        """
 
         def _elem2list(elem, return_children=False):
-            '''
+            """
             Copied from:
                 https://github.com/knadh/xmlutils.py/blob/master/xmlutils/xml2json.py
-            '''
+            """
             block = {}
 
             # get the element's children
@@ -162,7 +157,9 @@ def _elem2list(elem, return_children=False):
                 # create meaningful lists
                 scalar = False
                 try:
-                    if elem[0].tag != elem[1].tag:  # [{a: 1}, {b: 2}, {c: 3}] => {a: 1, b: 2, c: 3}
+                    if (
+                        elem[0].tag != elem[1].tag
+                    ):  # [{a: 1}, {b: 2}, {c: 3}] => {a: 1, b: 2, c: 3}
                         cur = dict(chain(*(d.items() for d in cur)))
                     else:
                         scalar = True
@@ -171,9 +168,15 @@ def _elem2list(elem, return_children=False):
 
                 if scalar:
                     if len(cur) > 1:
-                        cur = {elem[0].tag: [list(e.values())[0] for e in cur if list(e.values())[0] is not None]}
+                        cur = {
+                            elem[0].tag: [
+                                list(e.values())[0]
+                                for e in cur
+                                if list(e.values())[0] is not None
+                            ]
+                        }
                     else:
-                        cur = {elem[0].tag: list(cur[0].values())[0] }
+                        cur = {elem[0].tag: list(cur[0].values())[0]}
 
                 block[elem.tag] = cur
                 if return_children:
@@ -190,12 +193,12 @@ def _elem2list(elem, return_children=False):
 
         return _elem2list(self.root, return_children=True)
 
-
     def AsJson(self):
-        '''
+        """
         Returns the data-structure as a JSON.
 
         :return unicode:
-        '''
+        """
         import json
+
         return json.dumps(self.AsDict())


Test patch:
diff --git a/src/jobs_done10/_tests/__init__.py b/src/jobs_done10/_tests/__init__.py
index 8b13789..e69de29 100644
--- a/src/jobs_done10/_tests/__init__.py
+++ b/src/jobs_done10/_tests/__init__.py
@@ -1,1 +0,0 @@
-
diff --git a/src/jobs_done10/_tests/test_cli.py b/src/jobs_done10/_tests/test_cli.py
index 42c4e7e..a614b5d 100644
--- a/src/jobs_done10/_tests/test_cli.py
+++ b/src/jobs_done10/_tests/test_cli.py
@@ -1,8 +1,8 @@
-
+import pytest
 from _pytest.pytester import LineMatcher
 from click.testing import CliRunner
+
 from jobs_done10.cli import jobs_done
-import pytest
 
 
 @pytest.fixture
@@ -18,16 +18,16 @@ def test_help(cli_runner):
     """
     :type cli_runner: click.testing.CliRunner
     """
-    result = cli_runner.invoke(jobs_done, ['--help'])
+    result = cli_runner.invoke(jobs_done, ["--help"])
     assert result.exit_code == 0, result.output
     matcher = LineMatcher(result.output.splitlines())
-    matcher.fnmatch_lines([
-        'Usage: jobs_done10*',
-        '*',
-        'Options:',
-        '*',
-        'Commands:',
-        '*',
-    ])
-
-
+    matcher.fnmatch_lines(
+        [
+            "Usage: jobs_done10*",
+            "*",
+            "Options:",
+            "*",
+            "Commands:",
+            "*",
+        ]
+    )
diff --git a/src/jobs_done10/_tests/test_job_generator.py b/src/jobs_done10/_tests/test_job_generator.py
index 485f57e..d89d0db 100644
--- a/src/jobs_done10/_tests/test_job_generator.py
+++ b/src/jobs_done10/_tests/test_job_generator.py
@@ -1,67 +1,69 @@
-
-
 import contextlib
 
 import pytest
 
-from jobs_done10.job_generator import (JobGeneratorAttributeError,
-    JobGeneratorConfigurator)
+from jobs_done10.job_generator import JobGeneratorAttributeError
+from jobs_done10.job_generator import JobGeneratorConfigurator
 from jobs_done10.jobs_done_job import JobsDoneJob
 from jobs_done10.repository import Repository
 
 
 def testJobGeneratorConfigurator():
     class MyGenerator(object):
-
         def SetRepository(self, repository):
-            assert repository.url == 'http://repo.git'
+            assert repository.url == "http://repo.git"
 
         def SetMatrix(self, matrix, matrix_row):
-            assert matrix == {'id':[1,2,3]}
-            assert matrix_row == {'id':1}
+            assert matrix == {"id": [1, 2, 3]}
+            assert matrix_row == {"id": 1}
 
         def SetBuildBatchCommands(self, commands):
-            assert commands == ['command']
+            assert commands == ["command"]
 
         def Reset(self):
             pass
 
     jobs_done_job = JobsDoneJob()
-    jobs_done_job.matrix = {'id':[1,2,3]}
-    jobs_done_job.matrix_row = {'id':1}
-    jobs_done_job.repository = Repository(url='http://repo.git')
+    jobs_done_job.matrix = {"id": [1, 2, 3]}
+    jobs_done_job.matrix_row = {"id": 1}
+    jobs_done_job.repository = Repository(url="http://repo.git")
 
     generator = MyGenerator()
 
     # Test basic calls
-    with ExpectedCalls(generator, Reset=1, SetRepository=1, SetMatrix=1, SetBuildBatchCommands=0):
+    with ExpectedCalls(
+        generator, Reset=1, SetRepository=1, SetMatrix=1, SetBuildBatchCommands=0
+    ):
         JobGeneratorConfigurator.Configure(generator, jobs_done_job)
 
     # Set some more values to jobs_done_job, and make sure it is called
-    jobs_done_job.build_batch_commands = ['command']
-    with ExpectedCalls(generator, Reset=1, SetRepository=1, SetMatrix=1, SetBuildBatchCommands=1):
+    jobs_done_job.build_batch_commands = ["command"]
+    with ExpectedCalls(
+        generator, Reset=1, SetRepository=1, SetMatrix=1, SetBuildBatchCommands=1
+    ):
         JobGeneratorConfigurator.Configure(generator, jobs_done_job)
 
     # Try calling a missing option
-    jobs_done_job.boosttest_patterns = 'patterns'
+    jobs_done_job.boosttest_patterns = "patterns"
     with pytest.raises(JobGeneratorAttributeError):
         JobGeneratorConfigurator.Configure(generator, jobs_done_job)
 
 
-
-#===================================================================================================
+# ===================================================================================================
 # ExpectedCalls
-#===================================================================================================
+# ===================================================================================================
 @contextlib.contextmanager
 def ExpectedCalls(obj, **function_expected_calls):
     calls = {}
 
     def _GetWrapper(hash_, original_function):
         import functools
+
         @functools.wraps(original_function)
         def Wrapped(*args, **kwargs):
             calls[hash_][0] += 1
             original_function(*args, **kwargs)
+
         return Wrapped
 
     # __enter__
@@ -81,9 +83,13 @@ def Wrapped(*args, **kwargs):
     # __exit__
     try:
         for (_, function_name), (obtained, expected, _) in list(calls.items()):
-            assert obtained == expected, \
-                'Expected "%d" calls for function "%s", but got "%d"' % \
-                (expected, function_name, obtained)
+            assert (
+                obtained == expected
+            ), 'Expected "%d" calls for function "%s", but got "%d"' % (
+                expected,
+                function_name,
+                obtained,
+            )
     finally:
         # Clear all mocks
         for (obj, function_name), (_, _, original_function) in list(calls.items()):
diff --git a/src/jobs_done10/_tests/test_jobs_done_job.py b/src/jobs_done10/_tests/test_jobs_done_job.py
index 3305d61..d6152b2 100644
--- a/src/jobs_done10/_tests/test_jobs_done_job.py
+++ b/src/jobs_done10/_tests/test_jobs_done_job.py
@@ -1,19 +1,20 @@
-
-
 from textwrap import dedent
 
 import pytest
 
-from jobs_done10.jobs_done_job import (JobsDoneFileTypeError, JobsDoneJob,
-    UnknownJobsDoneFileOption, UnmatchableConditionError)
+from jobs_done10.jobs_done_job import JobsDoneFileTypeError
+from jobs_done10.jobs_done_job import JobsDoneJob
+from jobs_done10.jobs_done_job import UnknownJobsDoneFileOption
+from jobs_done10.jobs_done_job import UnmatchableConditionError
 from jobs_done10.repository import Repository
 
 
-_REPOSITORY = Repository(url='https://space.git', branch='milky_way')
+_REPOSITORY = Repository(url="https://space.git", branch="milky_way")
+
 
 def testCreateJobsDoneJobFromYAML():
     yaml_contents = dedent(
-        '''
+        """
         junit_patterns:
         - "junit*.xml"
 
@@ -42,7 +43,7 @@ def testCreateJobsDoneJobFromYAML():
 
           moon:
           - europa
-        '''
+        """
     )
     jobs_done_jobs = JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
 
@@ -50,52 +51,68 @@ def testCreateJobsDoneJobFromYAML():
     assert len(jobs_done_jobs) == 2
 
     def CheckCommonValues(jobs_done_job):
-        assert jobs_done_job.matrix == {'moon': ['europa'], 'planet': ['mercury', 'venus']}
-        assert jobs_done_job.junit_patterns == ['junit*.xml']
-        assert jobs_done_job.boosttest_patterns == ['cpptest*.xml']
-        assert jobs_done_job.parameters == [{
-            'choice' : {
-                'name': 'PARAM',
-                'choices': ['choice_1', 'choice_2'],
-                'description': 'Description',
-
+        assert jobs_done_job.matrix == {
+            "moon": ["europa"],
+            "planet": ["mercury", "venus"],
+        }
+        assert jobs_done_job.junit_patterns == ["junit*.xml"]
+        assert jobs_done_job.boosttest_patterns == ["cpptest*.xml"]
+        assert jobs_done_job.parameters == [
+            {
+                "choice": {
+                    "name": "PARAM",
+                    "choices": ["choice_1", "choice_2"],
+                    "description": "Description",
+                }
             }
-        }]
+        ]
 
     CheckCommonValues(jobs_done_jobs[0])
     CheckCommonValues(jobs_done_jobs[1])
 
     # Tests for specific jobs
-    mercury_job = jobs_done_jobs[0] if jobs_done_jobs[0].matrix_row['planet'] == 'mercury' else jobs_done_jobs[1]
-    venus_job = jobs_done_jobs[0] if jobs_done_jobs[0].matrix_row['planet'] == 'venus' else jobs_done_jobs[1]
+    mercury_job = (
+        jobs_done_jobs[0]
+        if jobs_done_jobs[0].matrix_row["planet"] == "mercury"
+        else jobs_done_jobs[1]
+    )
+    venus_job = (
+        jobs_done_jobs[0]
+        if jobs_done_jobs[0].matrix_row["planet"] == "venus"
+        else jobs_done_jobs[1]
+    )
 
-    assert mercury_job.matrix == {'moon': ['europa'], 'planet': ['mercury', 'venus']}
-    assert mercury_job.matrix_row == {'moon': 'europa', 'planet': 'mercury'}
+    assert mercury_job.matrix == {"moon": ["europa"], "planet": ["mercury", "venus"]}
+    assert mercury_job.matrix_row == {"moon": "europa", "planet": "mercury"}
 
-    assert venus_job.matrix == {'moon': ['europa'], 'planet': ['mercury', 'venus']}
-    assert venus_job.matrix_row == {'moon': 'europa', 'planet': 'venus'}
+    assert venus_job.matrix == {"moon": ["europa"], "planet": ["mercury", "venus"]}
+    assert venus_job.matrix_row == {"moon": "europa", "planet": "venus"}
 
     # In this case, our commands use some replacement variables, including variables defined in
     # 'matrix', and special cases 'name' and 'branch' based on repository.
     assert mercury_job.build_batch_commands == [
-        "command on planet mercury (repository 'space' on 'milky_way')"]
+        "command on planet mercury (repository 'space' on 'milky_way')"
+    ]
     assert venus_job.build_batch_commands == [
-        "command on planet venus (repository 'space' on 'milky_way')"]
+        "command on planet venus (repository 'space' on 'milky_way')"
+    ]
 
     # Check display_name
-    assert mercury_job.display_name == '[milky_way] mercury-europa space'
-    assert mercury_job.label_expression == 'planet-mercury&&moon-europa'
+    assert mercury_job.display_name == "[milky_way] mercury-europa space"
+    assert mercury_job.label_expression == "planet-mercury&&moon-europa"
 
     # Check labels
-    assert venus_job.display_name == '[milky_way] venus-europa space'
-    assert venus_job.label_expression == 'planet-venus&&moon-europa'
+    assert venus_job.display_name == "[milky_way] venus-europa space"
+    assert venus_job.label_expression == "planet-venus&&moon-europa"
 
 
 def testExclude():
-    key = lambda job: ':'.join(j + '-' + job.matrix_row[j] for j in sorted(job.matrix_row.keys()))
+    key = lambda job: ":".join(
+        j + "-" + job.matrix_row[j] for j in sorted(job.matrix_row.keys())
+    )
     # Base case ------------------------------------------------------------------------------------
     yaml_contents = dedent(
-        '''
+        """
         matrix:
           planet:
           - mercury
@@ -105,19 +122,19 @@ def testExclude():
           - europa
           - ganymede
 
-        '''
+        """
     )
     jobs_done_jobs = JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
     assert sorted(map(key, jobs_done_jobs)) == [
-        'moon-europa:planet-mercury',
-        'moon-europa:planet-venus',
-        'moon-ganymede:planet-mercury',
-        'moon-ganymede:planet-venus'
+        "moon-europa:planet-mercury",
+        "moon-europa:planet-venus",
+        "moon-ganymede:planet-mercury",
+        "moon-ganymede:planet-venus",
     ]
 
     # Exclude everything matching planet-venus -----------------------------------------------------
     yaml_contents = dedent(
-        '''
+        """
         matrix:
           planet:
           - mercury
@@ -128,17 +145,17 @@ def testExclude():
           - ganymede
 
         planet-venus:exclude: yes
-        '''
+        """
     )
     jobs_done_jobs = JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
     assert sorted(map(key, jobs_done_jobs)) == [
-        'moon-europa:planet-mercury',
-        'moon-ganymede:planet-mercury',
+        "moon-europa:planet-mercury",
+        "moon-ganymede:planet-mercury",
     ]
 
     # Exclude everything matching planet-venus and moon europa -------------------------------------
     yaml_contents = dedent(
-        '''
+        """
         matrix:
           planet:
           - mercury
@@ -149,18 +166,18 @@ def testExclude():
           - ganymede
 
         planet-venus:moon-europa:exclude: yes
-        '''
+        """
     )
     jobs_done_jobs = JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
     assert sorted(map(key, jobs_done_jobs)) == [
-        'moon-europa:planet-mercury',
-        'moon-ganymede:planet-mercury',
-        'moon-ganymede:planet-venus'
+        "moon-europa:planet-mercury",
+        "moon-ganymede:planet-mercury",
+        "moon-ganymede:planet-venus",
     ]
 
     # Exclude everything ---------------------------------------------------------------------------
     yaml_contents = dedent(
-        '''
+        """
         matrix:
           planet:
           - mercury
@@ -171,7 +188,7 @@ def testExclude():
           - ganymede
 
         exclude: yes
-        '''
+        """
     )
     jobs_done_jobs = JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
     assert sorted(map(key, jobs_done_jobs)) == []
@@ -179,7 +196,7 @@ def testExclude():
 
 def testBranchFlags():
     yaml_contents = dedent(
-        '''
+        """
         branch-master:build_shell_commands:
         - "master.sh"
 
@@ -188,28 +205,26 @@ def testBranchFlags():
 
         branch-with-hyphens-in-name:build_batch_commands:
         - "crazy.bat"
-        '''
+        """
     )
 
     jd_file = JobsDoneJob.CreateFromYAML(
         yaml_contents,
-        repository=Repository(url='https://space.git', branch='milky_way')
+        repository=Repository(url="https://space.git", branch="milky_way"),
     )[0]
     assert jd_file.build_shell_commands is None
-    assert jd_file.build_batch_commands == ['milky_way.bat']
-
+    assert jd_file.build_batch_commands == ["milky_way.bat"]
 
     jd_file = JobsDoneJob.CreateFromYAML(
-        yaml_contents,
-        repository=Repository(url='https://space.git', branch='master')
+        yaml_contents, repository=Repository(url="https://space.git", branch="master")
     )[0]
-    assert jd_file.build_shell_commands == ['master.sh']
+    assert jd_file.build_shell_commands == ["master.sh"]
     assert jd_file.build_batch_commands is None
 
 
 def testMatrixAndFlags():
     yaml_contents = dedent(
-        '''
+        """
         platform-windows:junit_patterns:
         - "junit*.xml"
 
@@ -223,22 +238,22 @@ def testMatrixAndFlags():
             platform:
             - linux
             - windows
-        '''
+        """
     )
     for jd_file in JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY):
-        if jd_file.matrix_row['platform'] == 'linux':
+        if jd_file.matrix_row["platform"] == "linux":
             assert jd_file.junit_patterns is None
             assert jd_file.build_batch_commands is None
-            assert jd_file.build_shell_commands == ['linux command']
+            assert jd_file.build_shell_commands == ["linux command"]
         else:
-            assert jd_file.junit_patterns == ['junit*.xml']
-            assert jd_file.build_batch_commands == ['windows command']
+            assert jd_file.junit_patterns == ["junit*.xml"]
+            assert jd_file.build_batch_commands == ["windows command"]
             assert jd_file.build_shell_commands is None
 
 
 def testMatrixAndRegexFlags():
     yaml_contents = dedent(
-        '''
+        """
         platform-win.*:junit_patterns:
         - "junit*.xml"
 
@@ -249,21 +264,21 @@ def testMatrixAndRegexFlags():
             platform:
             - linux
             - windows
-        '''
+        """
     )
     for jd_file in JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY):
-        if jd_file.matrix_row['platform'] == 'linux':
+        if jd_file.matrix_row["platform"] == "linux":
             assert jd_file.junit_patterns is None
             assert jd_file.build_batch_commands is None
-            assert jd_file.build_shell_commands == ['linux command']
+            assert jd_file.build_shell_commands == ["linux command"]
         else:
-            assert jd_file.junit_patterns == ['junit*.xml']
+            assert jd_file.junit_patterns == ["junit*.xml"]
             assert jd_file.build_shell_commands is None
 
 
 def testMatrixAndExtraFlags():
     yaml_contents = dedent(
-        '''
+        """
         platform-windows:junit_patterns:
         - "junit*.xml"
 
@@ -278,22 +293,22 @@ def testMatrixAndExtraFlags():
             - win32,windows
             - win64,windows
             - linux64,linux
-        '''
+        """
     )
     for jd_file in JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY):
-        if jd_file.matrix_row['platform'] == 'linux64':
+        if jd_file.matrix_row["platform"] == "linux64":
             assert jd_file.junit_patterns is None
             assert jd_file.build_batch_commands is None
-            assert jd_file.build_shell_commands == ['linux command: linux64']
-        if jd_file.matrix_row['platform'] == 'win32':
-            assert jd_file.junit_patterns == ['junit*.xml']
-            assert jd_file.build_batch_commands == ['windows command: win32']
+            assert jd_file.build_shell_commands == ["linux command: linux64"]
+        if jd_file.matrix_row["platform"] == "win32":
+            assert jd_file.junit_patterns == ["junit*.xml"]
+            assert jd_file.build_batch_commands == ["windows command: win32"]
             assert jd_file.build_shell_commands is None
 
 
 def testMatrixAndFlagsForSubDicts():
     yaml_contents = dedent(
-        '''
+        """
         git:
           platform-windows:shallow: true
           platform-linux:shallow: false
@@ -307,23 +322,19 @@ def testMatrixAndFlagsForSubDicts():
             platform:
             - linux
             - windows
-        '''
+        """
     )
     for jd_file in JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY):
-        if jd_file.matrix_row['platform'] == 'linux':
-            assert jd_file.git == {'shallow' : 'false'}
-            assert jd_file.additional_repositories == [
-                {'git' : {'shallow' : 'false'}}
-            ]
+        if jd_file.matrix_row["platform"] == "linux":
+            assert jd_file.git == {"shallow": "false"}
+            assert jd_file.additional_repositories == [{"git": {"shallow": "false"}}]
         else:
-            assert jd_file.git == {'shallow' : 'true'}
-            assert jd_file.additional_repositories == [
-                {'git' : {'shallow' : 'true'}}
-            ]
+            assert jd_file.git == {"shallow": "true"}
+            assert jd_file.additional_repositories == [{"git": {"shallow": "true"}}]
 
 
 def testRaiseWithAmbiguousConditionsAndDifferentValues():
-    '''
+    """
     When we match more than one line for the same key, we need to choose only one of them
     consistently regardless of the order in which they appear.
     To do that the conditions present on one line should be a superset of other one. In the end
@@ -354,9 +365,9 @@ def testRaiseWithAmbiguousConditionsAndDifferentValues():
         platform-windows:display_name: "slave2 job"
 
     They are not ambiguous as only one will match for every matrix combination.
-    '''
+    """
     yaml_contents = dedent(
-        '''
+        """
         matrix:
             platform:
             - linux
@@ -368,22 +379,22 @@ def testRaiseWithAmbiguousConditionsAndDifferentValues():
 
         platform-linux:display_name: "Linux job"
         slave-slave2:display_name: "slave2 job"
-        '''
+        """
     )
     with pytest.raises(ValueError):
         JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
 
 
 def testIgnoreAmbiguousConditionsWithEqualValues():
-    '''
+    """
     ..see: `testRaiseWithAmbiguousConditionsAndDifferentValues` for more details
 
     Even though we should raise when there are multiple matched lines with ambiguous conditions, we
     should ignore when the value does not change. This is very useful when you have a large matrix
     and need to set the same value for a lot of them.
-    '''
+    """
     yaml_contents = dedent(
-        '''
+        """
         matrix:
             platform:
             - linux
@@ -395,21 +406,24 @@ def testIgnoreAmbiguousConditionsWithEqualValues():
 
         platform-linux:display_name: "Foo job"
         slave-slave2:display_name: "Foo job"
-        '''
+        """
     )
     for jd_file in JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY):
-        if jd_file.matrix_row['platform'] == 'linux' or jd_file.matrix_row['slave'] == 'slave2':
-            assert jd_file.display_name == 'Foo job'
+        if (
+            jd_file.matrix_row["platform"] == "linux"
+            or jd_file.matrix_row["slave"] == "slave2"
+        ):
+            assert jd_file.display_name == "Foo job"
         else:
             assert jd_file.display_name is None
 
 
 def testAllowOverridingConditions():
-    '''
+    """
     ..see: `testRaiseWithAmbiguousConditionsAndDifferentValues` for more details
-    '''
+    """
     yaml_contents = dedent(
-        '''
+        """
         matrix:
             platform:
             - linux
@@ -422,56 +436,56 @@ def testAllowOverridingConditions():
         display_name: "Generic job"
         platform-linux:display_name: "Linux job"
         platform-linux:slave-slave2:display_name: "slave2 job"
-        '''
+        """
     )
     for jd_file in JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY):
-        if jd_file.matrix_row['platform'] == 'linux':
-            if jd_file.matrix_row['slave'] == 'slave1':
-                assert jd_file.display_name == 'Linux job'
+        if jd_file.matrix_row["platform"] == "linux":
+            if jd_file.matrix_row["slave"] == "slave1":
+                assert jd_file.display_name == "Linux job"
             else:
-                assert jd_file.display_name == 'slave2 job'
+                assert jd_file.display_name == "slave2 job"
         else:
-            assert jd_file.display_name == 'Generic job'
+            assert jd_file.display_name == "Generic job"
 
 
 def testBranchPatterns():
     base_contents = dedent(
-        '''
+        """
         matrix:
             planet:
             - mars
             - earth
             - venus
 
-        '''
+        """
     )
     # Using a pattern that does not match our branch will prevent jobs from being generated
     jd_file_contents = base_contents + dedent(
-        '''
+        """
         branch_patterns:
         - feature-.*
-        '''
+        """
     )
     jobs = JobsDoneJob.CreateFromYAML(jd_file_contents, repository=_REPOSITORY)
     assert len(jobs) == 0
 
     # Matching patterns work as usual
     jd_file_contents = base_contents + dedent(
-        '''
+        """
         branch_patterns:
         - milky_way
-        '''
+        """
     )
     jobs = JobsDoneJob.CreateFromYAML(jd_file_contents, repository=_REPOSITORY)
     assert len(jobs) == 3
 
     # Also works with several patterns and regexes
     jd_file_contents = base_contents + dedent(
-        '''
+        """
         branch_patterns:
         - master
         - milky.*
-        '''
+        """
     )
     jobs = JobsDoneJob.CreateFromYAML(jd_file_contents, repository=_REPOSITORY)
     assert len(jobs) == 3
@@ -479,7 +493,7 @@ def testBranchPatterns():
     # Branch patterns can also be filtered using matrix
     # e.g., mars only has jobs in master
     jd_file_contents = base_contents + dedent(
-        '''
+        """
         planet-mars:branch_patterns:
         - "master"
 
@@ -488,63 +502,64 @@ def testBranchPatterns():
 
         planet-venus:branch_patterns:
         - ".*"
-        '''
+        """
     )
     jobs = JobsDoneJob.CreateFromYAML(jd_file_contents, repository=_REPOSITORY)
     assert len(jobs) == 2
 
 
-
 def testUnknownOption():
     # Unknown options should fail
     yaml_contents = dedent(
-        '''
+        """
         bad_option: value
-        '''
+        """
     )
     with pytest.raises(UnknownJobsDoneFileOption) as e:
         JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
 
-    assert e.value.option_name == 'bad_option'
+    assert e.value.option_name == "bad_option"
 
 
 def testTypeChecking():
     # List is the correct type for build_batch_commands
     yaml_contents = dedent(
-        '''
+        """
         build_batch_commands:
         - "list item 1"
-        '''
+        """
     )
     JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
 
     # Trying to set a different value, should raise an error
     yaml_contents = dedent(
-        '''
+        """
         build_batch_commands: "string item"
-        '''
+        """
     )
     with pytest.raises(JobsDoneFileTypeError) as e:
         JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY)
 
-    assert e.value.option_name == 'build_batch_commands'
-    assert e.value.accepted_types == [JobsDoneJob.PARSEABLE_OPTIONS['build_batch_commands']]
+    assert e.value.option_name == "build_batch_commands"
+    assert e.value.accepted_types == [
+        JobsDoneJob.PARSEABLE_OPTIONS["build_batch_commands"]
+    ]
     assert e.value.obtained_type == str
-    assert e.value.option_value == 'string item'
+    assert e.value.option_value == "string item"
 
 
 def testCreateFromFile(tmpdir):
     contents = dedent(
-        '''
+        """
         matrix:
             planet:
             - mars
             - earth
             - venus
 
-        '''
+        """
     )
-    f = tmpdir / '.jobs_done.yaml'
+    f = tmpdir / ".jobs_done.yaml"
     f.write(contents)
 
     jobs = JobsDoneJob.CreateFromFile(str(f), repository=_REPOSITORY)
@@ -553,28 +568,28 @@ def testCreateFromFile(tmpdir):
 
 
 def testStringConversion():
-    '''
+    """
     Asserts that our YAML parser converts all basic values to strings (non unicode)
-    '''
+    """
     contents = dedent(
-        '''
+        """
         junit_patterns:
             - 1
-        '''
+        """
     )
     jobs = JobsDoneJob.CreateFromYAML(contents, repository=_REPOSITORY)
 
     assert jobs[0].junit_patterns != [1]
-    assert jobs[0].junit_patterns == ['1']
+    assert jobs[0].junit_patterns == ["1"]
 
 
 def testIgnoreUnmatchable():
-    '''
+    """
     Asserts that using a condition that can never be matched will not raise an error if
     'ignore_unmatchable' is enabled.
-    '''
+    """
     contents = dedent(
-        '''
+        """
         ignore_unmatchable: true
 
         planet-pluto:junit_patterns:
@@ -583,49 +598,48 @@ def testIgnoreUnmatchable():
         matrix:
             planet:
             - earth
-        '''
+        """
     )
     JobsDoneJob.CreateFromYAML(contents, repository=_REPOSITORY)
 
 
 def testUnmatchableCondition():
-    '''
+    """
     Asserts that using a condition that can never be matched will raise an error.
-    '''
+    """
     contents = dedent(
-        '''
+        """
         planet-pluto:junit_patterns:
             - '*.xml'
 
         matrix:
             planet:
             - earth
-        '''
+        """
     )
     with pytest.raises(UnmatchableConditionError) as e:
         JobsDoneJob.CreateFromYAML(contents, repository=_REPOSITORY)
 
-    assert e.value.option == 'planet-pluto:junit_patterns'
+    assert e.value.option == "planet-pluto:junit_patterns"
 
 
 def testUnmatchableSubCondition():
     contents = dedent(
-        '''
+        """
         git:
             planet-pluto:shallow: true
 
         matrix:
             planet:
             - earth
-        '''
+        """
     )
     with pytest.raises(UnmatchableConditionError) as e:
         JobsDoneJob.CreateFromYAML(contents, repository=_REPOSITORY)
-    assert e.value.option == 'planet-pluto:shallow'
-
+    assert e.value.option == "planet-pluto:shallow"
 
     contents = dedent(
-        '''
+        """
         additional_repositories:
         - git:
             planet-pluto:shallow: true
@@ -633,22 +647,22 @@ def testUnmatchableSubCondition():
         matrix:
             planet:
             - earth
-        '''
+        """
     )
     with pytest.raises(UnmatchableConditionError) as e:
         JobsDoneJob.CreateFromYAML(contents, repository=_REPOSITORY)
-    assert e.value.option == 'planet-pluto:shallow'
+    assert e.value.option == "planet-pluto:shallow"
 
 
 def testStripFile():
-    '''
+    """
     Asserts that we can handle empty spaces and tabs in .yaml files, without having parse errors
-    '''
+    """
     contents = dedent(
-        '''
+        """
         junit_patterns:
             - 1
-        '''
+        """
     )
-    contents += '\t'
+    contents += "\t"
     JobsDoneJob.CreateFromYAML(contents, repository=_REPOSITORY)
diff --git a/src/jobs_done10/_tests/test_repository.py b/src/jobs_done10/_tests/test_repository.py
index 30d1cc1..7ceb51f 100644
--- a/src/jobs_done10/_tests/test_repository.py
+++ b/src/jobs_done10/_tests/test_repository.py
@@ -1,36 +1,34 @@
-
-
 from jobs_done10.repository import Repository
 
 
 def testNameFromURL():
     tests = [
-        ('/path/to/repo.git/', 'repo'),
-        ('file:///path/to/repo.git/', 'repo'),
-        ('file://~/path/to/repo.git/', 'repo'),
-        ('git://host.xz/path/to/repo.git/', 'repo'),
-        ('git://host.xz/~user/path/to/repo.git/', 'repo'),
-        ('host.xz:/path/to/repo.git/', 'repo'),
-        ('host.xz:path/to/repo.git', 'repo'),
-        ('host.xz:~user/path/to/repo.git/', 'repo'),
-        ('http://host.xz/path/to/repo.git/', 'repo'),
-        ('https://host.xz/path/to/repo.git/', 'repo'),
-        ('path/to/repo.git/', 'repo'),
-        ('rsync://host.xz/path/to/repo.git/', 'repo'),
-        ('ssh://host.xz/path/to/repo.git/', 'repo'),
-        ('ssh://host.xz/path/to/repo.git/', 'repo'),
-        ('ssh://host.xz/~/path/to/repo.git', 'repo'),
-        ('ssh://host.xz/~user/path/to/repo.git/', 'repo'),
-        ('ssh://host.xz:port/path/to/repo.git/', 'repo'),
-        ('ssh://user@host.xz/path/to/repo.git/', 'repo'),
-        ('ssh://user@host.xz/path/to/repo.git/', 'repo'),
-        ('ssh://user@host.xz/~/path/to/repo.git', 'repo'),
-        ('ssh://user@host.xz/~user/path/to/repo.git/', 'repo'),
-        ('ssh://user@host.xz:port/path/to/repo.git/', 'repo'),
-        ('user@host.xz:/path/to/repo.git/', 'repo'),
-        ('user@host.xz:path/to/repo.git', 'repo'),
-        ('user@host.xz:~user/path/to/repo.git/', 'repo'),
-        ('~/path/to/repo.git', 'repo'),
+        ("/path/to/repo.git/", "repo"),
+        ("file:///path/to/repo.git/", "repo"),
+        ("file://~/path/to/repo.git/", "repo"),
+        ("git://host.xz/path/to/repo.git/", "repo"),
+        ("git://host.xz/~user/path/to/repo.git/", "repo"),
+        ("host.xz:/path/to/repo.git/", "repo"),
+        ("host.xz:path/to/repo.git", "repo"),
+        ("host.xz:~user/path/to/repo.git/", "repo"),
+        ("http://host.xz/path/to/repo.git/", "repo"),
+        ("https://host.xz/path/to/repo.git/", "repo"),
+        ("path/to/repo.git/", "repo"),
+        ("rsync://host.xz/path/to/repo.git/", "repo"),
+        ("ssh://host.xz/path/to/repo.git/", "repo"),
+        ("ssh://host.xz/path/to/repo.git/", "repo"),
+        ("ssh://host.xz/~/path/to/repo.git", "repo"),
+        ("ssh://host.xz/~user/path/to/repo.git/", "repo"),
+        ("ssh://host.xz:port/path/to/repo.git/", "repo"),
+        ("ssh://user@host.xz/path/to/repo.git/", "repo"),
+        ("ssh://user@host.xz/path/to/repo.git/", "repo"),
+        ("ssh://user@host.xz/~/path/to/repo.git", "repo"),
+        ("ssh://user@host.xz/~user/path/to/repo.git/", "repo"),
+        ("ssh://user@host.xz:port/path/to/repo.git/", "repo"),
+        ("user@host.xz:/path/to/repo.git/", "repo"),
+        ("user@host.xz:path/to/repo.git", "repo"),
+        ("user@host.xz:~user/path/to/repo.git/", "repo"),
+        ("~/path/to/repo.git", "repo"),
     ]
 
     for url, expected_name in tests:
@@ -39,12 +37,16 @@ def testNameFromURL():
 
 def testEquality():
     assert Repository() == Repository()
-    assert Repository(url='http://example.com') == Repository(url='http://example.com')
-    assert Repository(url='http://example.com', branch='foo') != Repository(url='http://example.com')
-    assert Repository(url='http://example.com') != Repository(url='http://other.com')
-
-    assert Repository(url='http://example.com', branch='bar') == \
-           Repository(url='http://example.com', branch='bar')
-
-    assert Repository(url='http://example.com', branch='foo') != \
-           Repository(url='http://example.com', branch='bar')
+    assert Repository(url="http://example.com") == Repository(url="http://example.com")
+    assert Repository(url="http://example.com", branch="foo") != Repository(
+        url="http://example.com"
+    )
+    assert Repository(url="http://example.com") != Repository(url="http://other.com")
+
+    assert Repository(url="http://example.com", branch="bar") == Repository(
+        url="http://example.com", branch="bar"
+    )
+
+    assert Repository(url="http://example.com", branch="foo") != Repository(
+        url="http://example.com", branch="bar"
+    )
diff --git a/src/jobs_done10/generators/_tests/__init__.py b/src/jobs_done10/generators/_tests/__init__.py
index 8b13789..e69de29 100644
--- a/src/jobs_done10/generators/_tests/__init__.py
+++ b/src/jobs_done10/generators/_tests/__init__.py
@@ -1,1 +0,0 @@
-
diff --git a/src/jobs_done10/generators/_tests/test_jenkins.py b/src/jobs_done10/generators/_tests/test_jenkins.py
index 6ce8e2a..f1ca70f 100644
--- a/src/jobs_done10/generators/_tests/test_jenkins.py
+++ b/src/jobs_done10/generators/_tests/test_jenkins.py
@@ -1,5 +1,3 @@
-
-
 import difflib
 import os
 import re
@@ -8,27 +6,33 @@
 
 import jenkins
 import pytest
-from jobs_done10.generators.jenkins import (
-    GetJobsFromDirectory, GetJobsFromFile, JenkinsJob, JenkinsJobPublisher, JenkinsXmlJobGenerator,
-    UploadJobsFromFile)
+
+from jobs_done10.generators.jenkins import GetJobsFromDirectory
+from jobs_done10.generators.jenkins import GetJobsFromFile
+from jobs_done10.generators.jenkins import JenkinsJob
+from jobs_done10.generators.jenkins import JenkinsJobPublisher
+from jobs_done10.generators.jenkins import JenkinsXmlJobGenerator
+from jobs_done10.generators.jenkins import UploadJobsFromFile
 from jobs_done10.job_generator import JobGeneratorConfigurator
-from jobs_done10.jobs_done_job import JOBS_DONE_FILENAME, JobsDoneFileTypeError, JobsDoneJob
+from jobs_done10.jobs_done_job import JOBS_DONE_FILENAME
+from jobs_done10.jobs_done_job import JobsDoneFileTypeError
+from jobs_done10.jobs_done_job import JobsDoneJob
 from jobs_done10.repository import Repository
 
 
-#===================================================================================================
+# ===================================================================================================
 # TestJenkinsXmlJobGenerator
-#===================================================================================================
+# ===================================================================================================
 class TestJenkinsXmlJobGenerator(object):
 
-    #===============================================================================================
+    # ===============================================================================================
     # Setup for common results in all tests
-    #===============================================================================================
+    # ===============================================================================================
     # Baseline expected XML. All tests are compared against this baseline, this way each test only
     # has to verify what is expected to be different, this way, if the baseline is changed, we don't
     # have to fix all tests.
     BASIC_EXPECTED_XML = dedent(
-        '''\
+        """\
         <?xml version="1.0" ?>
         <project>
           <description>&lt;!-- Managed by Job's Done --&gt;</description>
@@ -66,23 +70,21 @@ class TestJenkinsXmlJobGenerator(object):
             <localBranch>not_master</localBranch>
           </scm>
           <assignedNode>fake</assignedNode>
-        </project>''',
+        </project>""",
     )
 
-
-    #===============================================================================================
+    # ===============================================================================================
     # Tests
-    #===============================================================================================
+    # ===============================================================================================
     def testEmpty(self):
         with pytest.raises(ValueError) as e:
-            self._DoTest(yaml_contents='', expected_diff=None)
-        assert str(e.value) == 'Could not parse anything from .yaml contents'
-
+            self._DoTest(yaml_contents="", expected_diff=None)
+        assert str(e.value) == "Could not parse anything from .yaml contents"
 
     def testChoiceParameters(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''\
+                """\
                 parameters:
                   - choice:
                       name: "PARAM"
@@ -90,10 +92,10 @@ def testChoiceParameters(self):
                       - "choice_1"
                       - "choice_2"
                       description: "Description"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <properties>
                 +    <hudson.model.ParametersDefinitionProperty>
@@ -110,14 +112,14 @@ def testChoiceParameters(self):
                 +        </hudson.model.ChoiceParameterDefinition>
                 +      </parameterDefinitions>
                 +    </hudson.model.ParametersDefinitionProperty>
-                +  </properties>'''
+                +  </properties>"""
             ),
         )
 
     def testMultipleChoiceParameters(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 parameters:
                   - choice:
                       name: "PARAM"
@@ -131,10 +133,10 @@ def testMultipleChoiceParameters(self):
                       - "choice_1"
                       - "choice_2"
                       description: "Description"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <properties>
                 +    <hudson.model.ParametersDefinitionProperty>
@@ -161,24 +163,23 @@ def testMultipleChoiceParameters(self):
                 +        </hudson.model.ChoiceParameterDefinition>
                 +      </parameterDefinitions>
                 +    </hudson.model.ParametersDefinitionProperty>
-                +  </properties>'''
+                +  </properties>"""
             ),
         )
 
-
     def testStringParameters(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 parameters:
                   - string:
                       name: "PARAM_VERSION"
                       default: "Default"
                       description: "Description"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <properties>
                 +    <hudson.model.ParametersDefinitionProperty>
@@ -190,15 +191,14 @@ def testStringParameters(self):
                 +        </hudson.model.StringParameterDefinition>
                 +      </parameterDefinitions>
                 +    </hudson.model.ParametersDefinitionProperty>
-                +  </properties>'''
+                +  </properties>"""
             ),
         )
 
-
     def testMultipleStringParameters(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 parameters:
                   - string:
                       name: "PARAM_VERSION"
@@ -208,10 +208,10 @@ def testMultipleStringParameters(self):
                       name: "PARAM_VERSION_2"
                       default: "Default"
                       description: "Description"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <properties>
                 +    <hudson.model.ParametersDefinitionProperty>
@@ -228,15 +228,14 @@ def testMultipleStringParameters(self):
                 +        </hudson.model.StringParameterDefinition>
                 +      </parameterDefinitions>
                 +    </hudson.model.ParametersDefinitionProperty>
-                +  </properties>'''
+                +  </properties>"""
             ),
         )
 
-
     def testParametersMaintainOrder(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 parameters:
                   - choice:
                       name: "PARAM"
@@ -248,10 +247,10 @@ def testParametersMaintainOrder(self):
                       name: "PARAM_VERSION"
                       default: "Default"
                       description: "Description"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <properties>
                 +    <hudson.model.ParametersDefinitionProperty>
@@ -273,22 +272,21 @@ def testParametersMaintainOrder(self):
                 +        </hudson.model.StringParameterDefinition>
                 +      </parameterDefinitions>
                 +    </hudson.model.ParametersDefinitionProperty>
-                +  </properties>'''
+                +  </properties>"""
             ),
         )
 
-
     def testJUnitPatterns(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 junit_patterns:
                 - "junit*.xml"
                 - "others.xml"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <xunit>
@@ -323,41 +321,38 @@ def testJUnitPatterns(self):
                 +        </hudson.plugins.ws__cleanup.Pattern>
                 +      </patterns>
                 +    </hudson.plugins.ws__cleanup.PreBuildCleanup>
-                +  </buildWrappers>'''
+                +  </buildWrappers>"""
             ),
-
         )
 
-
     def testTimeoutAbsolute(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 timeout: 60
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <buildWrappers>
                 +    <hudson.plugins.build__timeout.BuildTimeoutWrapper>
                 +      <timeoutMinutes>60</timeoutMinutes>
                 +      <failBuild>true</failBuild>
                 +    </hudson.plugins.build__timeout.BuildTimeoutWrapper>
-                +  </buildWrappers>'''
+                +  </buildWrappers>"""
             ),
         )
 
-
     def testTimeoutNoActivity(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 timeout_no_activity: 600
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <buildWrappers>
                 +    <hudson.plugins.build__timeout.BuildTimeoutWrapper>
@@ -368,50 +363,48 @@ def testTimeoutNoActivity(self):
                 +        <hudson.plugins.build__timeout.operations.FailOperation/>
                 +      </operationList>
                 +    </hudson.plugins.build__timeout.BuildTimeoutWrapper>
-                +  </buildWrappers>'''
+                +  </buildWrappers>"""
             ),
         )
 
-
     def testCustomWorkspace(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 custom_workspace: workspace/WS
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
-                +  <customWorkspace>workspace/WS</customWorkspace>'''
+                +  <customWorkspace>workspace/WS</customWorkspace>"""
             ),
         )
 
     def testAuthToken(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 auth_token: my_token
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
-                +  <authToken>my_token</authToken>'''
+                +  <authToken>my_token</authToken>"""
             ),
         )
 
-
     def testBoosttestPatterns(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 boosttest_patterns:
                 - "boost*.xml"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <xunit>
@@ -442,21 +435,20 @@ def testBoosttestPatterns(self):
                 +        </hudson.plugins.ws__cleanup.Pattern>
                 +      </patterns>
                 +    </hudson.plugins.ws__cleanup.PreBuildCleanup>
-                +  </buildWrappers>'''
+                +  </buildWrappers>"""
             ),
         )
 
-
     def testJSUnitPatterns(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 jsunit_patterns:
                 - "jsunit*.xml"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <xunit>
@@ -487,24 +479,23 @@ def testJSUnitPatterns(self):
                 +        </hudson.plugins.ws__cleanup.Pattern>
                 +      </patterns>
                 +    </hudson.plugins.ws__cleanup.PreBuildCleanup>
-                +  </buildWrappers>'''
+                +  </buildWrappers>"""
             ),
         )
 
-
     def testMultipleTestResults(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 junit_patterns:
                 - "junit*.xml"
 
                 boosttest_patterns:
                 - "boosttest*.xml"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <xunit>
@@ -546,16 +537,16 @@ def testMultipleTestResults(self):
                 +        </hudson.plugins.ws__cleanup.Pattern>
                 +      </patterns>
                 +    </hudson.plugins.ws__cleanup.PreBuildCleanup>
-                +  </buildWrappers>'''
+                +  </buildWrappers>"""
             ),
         )
 
     @pytest.mark.parametrize(
-        'job_done_key, xml_key',
+        "job_done_key, xml_key",
         [
-            ('build_batch_commands', 'hudson.tasks.BatchFile'),
-            ('build_shell_commands', 'hudson.tasks.Shell'),
-            ('build_python_commands', 'hudson.plugins.python.Python'),
+            ("build_batch_commands", "hudson.tasks.BatchFile"),
+            ("build_shell_commands", "hudson.tasks.Shell"),
+            ("build_python_commands", "hudson.plugins.python.Python"),
         ],
     )
     def testBuildCommandsExpandNestedLists(self, job_done_key, xml_key):
@@ -563,37 +554,37 @@ def testBuildCommandsExpandNestedLists(self, job_done_key, xml_key):
         # sanity: no ref
         self._DoTest(
             yaml_contents=dedent(
-                f'''
+                f"""
                 branch-foo:{job_done_key}:
                 - someone else command
                 {job_done_key}:
                 - my_command
-                '''
+                """
             ),
             expected_diff=dedent(
-                f'''\
+                f"""\
                 @@ @@
                 +  <builders>
                 +    <{xml_key}>
                 +      <command>my_command</command>
                 +    </{xml_key}>
-                +  </builders>'''
+                +  </builders>"""
             ),
         )
 
         # expand refs (after)
         self._DoTest(
             yaml_contents=dedent(
-                f'''
+                f"""
                 branch-foo:{job_done_key}: &ref_a
                 - someone else command
                 {job_done_key}:
                 - my_command
                 - *ref_a
-                '''
+                """
             ),
             expected_diff=dedent(
-                f'''\
+                f"""\
                 @@ @@
                 +  <builders>
                 +    <{xml_key}>
@@ -602,23 +593,23 @@ def testBuildCommandsExpandNestedLists(self, job_done_key, xml_key):
                 +    <{xml_key}>
                 +      <command>someone else command</command>
                 +    </{xml_key}>
-                +  </builders>'''
+                +  </builders>"""
             ),
         )
 
         # expand refs (before)
         self._DoTest(
             yaml_contents=dedent(
-                f'''
+                f"""
                 branch-foo:{job_done_key}: &ref_a
                 - someone else command
                 {job_done_key}:
                 - *ref_a
                 - my_command
-                '''
+                """
             ),
             expected_diff=dedent(
-                f'''\
+                f"""\
                 @@ @@
                 +  <builders>
                 +    <{xml_key}>
@@ -627,7 +618,7 @@ def testBuildCommandsExpandNestedLists(self, job_done_key, xml_key):
                 +    <{xml_key}>
                 +      <command>my_command</command>
                 +    </{xml_key}>
-                +  </builders>'''
+                +  </builders>"""
             ),
         )
 
@@ -635,57 +626,55 @@ def testBuildBatchCommand(self):
         # works with a single command
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 build_batch_commands:
                 - my_command
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <builders>
                 +    <hudson.tasks.BatchFile>
                 +      <command>my_command</command>
                 +    </hudson.tasks.BatchFile>
-                +  </builders>'''
+                +  </builders>"""
             ),
-
         )
 
         # Works with multi line commands
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 build_batch_commands:
                 - |
                   multi_line
                   command
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <builders>
                 +    <hudson.tasks.BatchFile>
                 +      <command>multi_line&#xd;
                 +command</command>
                 +    </hudson.tasks.BatchFile>
-                +  </builders>'''
+                +  </builders>"""
             ),
-
         )
 
         # Works with multiple commands
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 build_batch_commands:
                 - command_1
                 - command_2
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <builders>
                 +    <hudson.tasks.BatchFile>
@@ -694,89 +683,83 @@ def testBuildBatchCommand(self):
                 +    <hudson.tasks.BatchFile>
                 +      <command>command_2</command>
                 +    </hudson.tasks.BatchFile>
-                +  </builders>'''
+                +  </builders>"""
             ),
-
         )
 
-
     def testBuildPythonCommand(self):
         # works with a single command
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 build_python_commands:
                 - print 'hello'
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <builders>
                 +    <hudson.plugins.python.Python>
                 +      <command>print 'hello'</command>
                 +    </hudson.plugins.python.Python>
-                +  </builders>'''
+                +  </builders>"""
             ),
-
         )
 
-
     def testBuildShellCommand(self):
         # works with a single command
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 build_shell_commands:
                 - my_command
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <builders>
                 +    <hudson.tasks.Shell>
                 +      <command>my_command</command>
                 +    </hudson.tasks.Shell>
-                +  </builders>'''
+                +  </builders>"""
             ),
-
         )
 
         # Works with multi line commands
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 build_shell_commands:
                 - |
                   multi_line
                   command
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <builders>
                 +    <hudson.tasks.Shell>
                 +      <command>multi_line
                 +command</command>
                 +    </hudson.tasks.Shell>
-                +  </builders>'''
+                +  </builders>"""
             ),
-
         )
 
         # Works with multiple commands
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 build_shell_commands:
                 - command_1
                 - command_2
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <builders>
                 +    <hudson.tasks.Shell>
@@ -785,21 +768,19 @@ def testBuildShellCommand(self):
                 +    <hudson.tasks.Shell>
                 +      <command>command_2</command>
                 +    </hudson.tasks.Shell>
-                +  </builders>'''
+                +  </builders>"""
             ),
-
         )
 
-
     def testDescriptionRegex(self):
         self._DoTest(
             yaml_contents=dedent(
-                r'''
+                r"""
                 description_regex: "JENKINS DESCRIPTION\\: (.*)"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <hudson.plugins.descriptionsetter.DescriptionSetterPublisher>
@@ -807,24 +788,22 @@ def testDescriptionRegex(self):
                 +      <regexpForFailed>JENKINS DESCRIPTION\\: (.*)</regexpForFailed>
                 +      <setForMatrix>false</setForMatrix>
                 +    </hudson.plugins.descriptionsetter.DescriptionSetterPublisher>
-                +  </publishers>'''
+                +  </publishers>"""
             ),
-
         )
 
-
     def testNotifyStash(self):
         self._DoTest(
             yaml_contents=dedent(
-                r'''
+                r"""
                 notify_stash:
                   url: stash.com
                   username: user
                   password: pass
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <org.jenkinsci.plugins.stashNotifier.StashNotifier>
@@ -832,43 +811,40 @@ def testNotifyStash(self):
                 +      <stashUserName>user</stashUserName>
                 +      <stashUserPassword>pass</stashUserPassword>
                 +    </org.jenkinsci.plugins.stashNotifier.StashNotifier>
-                +  </publishers>'''
+                +  </publishers>"""
             ),
-
         )
 
-
     def testNotifyStashServerDefault(self):
-        '''
+        """
         When given no parameters, use the default Stash configurations set in the Jenkins server
-        '''
+        """
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 notify_stash: stash.com
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <org.jenkinsci.plugins.stashNotifier.StashNotifier>
                 +      <stashServerBaseUrl>stash.com</stashServerBaseUrl>
                 +    </org.jenkinsci.plugins.stashNotifier.StashNotifier>
-                +  </publishers>'''
+                +  </publishers>"""
             ),
         )
 
-
     def testNotifyStashWithTests(self):
-        '''
+        """
         When we have both notify_stash, and some test pattern, we have to make sure that the output
         jenkins job xml places the notify_stash publisher AFTER the test publisher, otherwise builds
         with failed tests might be reported as successful to Stash
-        '''
+        """
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 notify_stash:
                   url: stash.com
                   username: user
@@ -876,10 +852,10 @@ def testNotifyStashWithTests(self):
 
                 jsunit_patterns:
                 - "jsunit*.xml"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <xunit>
@@ -915,14 +891,13 @@ def testNotifyStashWithTests(self):
                 +        </hudson.plugins.ws__cleanup.Pattern>
                 +      </patterns>
                 +    </hudson.plugins.ws__cleanup.PreBuildCleanup>
-                +  </buildWrappers>'''
+                +  </buildWrappers>"""
             ),
         )
 
-
     def testMatrix(self):
         yaml_contents = dedent(
-            '''
+            """
             planet-earth:build_shell_commands:
             - earth_command
 
@@ -936,9 +911,9 @@ def testMatrix(self):
 
                 moon:
                 - europa
-            '''
+            """
         )
-        repository = Repository(url='http://fake.git', branch='not_master')
+        repository = Repository(url="http://fake.git", branch="not_master")
 
         # This test should create two jobs based on the given matrix
         jobs_done_jobs = JobsDoneJob.CreateFromYAML(yaml_contents, repository)
@@ -949,15 +924,15 @@ def testMatrix(self):
             JobGeneratorConfigurator.Configure(job_generator, jd_file)
             jenkins_job = job_generator.GetJob()
 
-            planet = jd_file.matrix_row['planet']
+            planet = jd_file.matrix_row["planet"]
 
             # Matrix affects the jobs name, but single value rows are left out
-            assert jenkins_job.name == 'fake-not_master-%(planet)s' % locals()
+            assert jenkins_job.name == "fake-not_master-%(planet)s" % locals()
 
             self._AssertDiff(
                 jenkins_job.xml,
                 dedent(
-                    '''\
+                    """\
                     @@ @@
                     -  <assignedNode>fake</assignedNode>
                     +  <assignedNode>fake-%(planet)s</assignedNode>
@@ -965,23 +940,23 @@ def testMatrix(self):
                     +    <hudson.tasks.Shell>
                     +      <command>%(planet)s_command</command>
                     +    </hudson.tasks.Shell>
-                    +  </builders>''' % locals()
+                    +  </builders>"""
+                    % locals()
                 ),
             )
 
-
     def testMatrixSingleValueOnly(self):
         yaml_contents = dedent(
-            '''
+            """
             matrix:
                 planet:
                 - earth
 
                 moon:
                 - europa
-            '''
+            """
         )
-        repository = Repository(url='http://fake.git', branch='not_master')
+        repository = Repository(url="http://fake.git", branch="not_master")
 
         # This test should create two jobs based on the given matrix
         jd_file = JobsDoneJob.CreateFromYAML(yaml_contents, repository)[0]
@@ -991,99 +966,94 @@ def testMatrixSingleValueOnly(self):
         jenkins_job = job_generator.GetJob()
 
         # Matrix usually affects the jobs name, but single value rows are left out
-        assert jenkins_job.name == 'fake-not_master'
+        assert jenkins_job.name == "fake-not_master"
 
         # XML should have no diff too, because single values do not affect label_expression
-        self._AssertDiff(jenkins_job.xml, '')
-
+        self._AssertDiff(jenkins_job.xml, "")
 
     def testDisplayName(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 display_name: "{name}-{branch}"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
-                +  <displayName>fake-not_master</displayName>'''
+                +  <displayName>fake-not_master</displayName>"""
             ),
         )
 
-
     def testLabelExpression(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 label_expression: "win32&&dist-12.0"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 -  <assignedNode>fake</assignedNode>
-                +  <assignedNode>win32&amp;&amp;dist-12.0</assignedNode>'''
+                +  <assignedNode>win32&amp;&amp;dist-12.0</assignedNode>"""
             ),
         )
 
-
     def testCron(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 cron: |
                        # Everyday at 22 pm
                        0 22 * * *
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <triggers>
                 +    <hudson.triggers.TimerTrigger>
                 +      <spec># Everyday at 22 pm
                 +0 22 * * *</spec>
                 +    </hudson.triggers.TimerTrigger>
-                +  </triggers>'''
+                +  </triggers>"""
             ),
         )
 
-
     def testSCMPoll(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 scm_poll: |
                        # Everyday at 22 pm
                        0 22 * * *
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <triggers>
                 +    <hudson.triggers.SCMTrigger>
                 +      <spec># Everyday at 22 pm
                 +0 22 * * *</spec>
                 +    </hudson.triggers.SCMTrigger>
-                +  </triggers>'''
+                +  </triggers>"""
             ),
         )
 
-
     def testAdditionalRepositories(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 additional_repositories:
                 - git:
                     url: http://some_url.git
                     branch: my_branch
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 -  <scm class="hudson.plugins.git.GitSCM">
                 -    <configVersion>2</configVersion>
@@ -1154,18 +1124,17 @@ def testAdditionalRepositories(self):
                 +      </hudson.plugins.git.GitSCM>
                 +    </scms>
                 @@ @@
-                -  <assignedNode>fake</assignedNode>'''
+                -  <assignedNode>fake</assignedNode>"""
             ),
         )
 
-
     def testGitAndAdditionalRepositories(self):
-        '''
+        """
         Make sure that everything works just fine when we mix 'git' and 'additional_repositories'
-        '''
+        """
         # We expect the same diff for both orders (git -> additional and additional -> git)
         expected_diff = dedent(
-            '''\
+            """\
             @@ @@
             -  <scm class="hudson.plugins.git.GitSCM">
             -    <configVersion>2</configVersion>
@@ -1237,13 +1206,13 @@ def testGitAndAdditionalRepositories(self):
             +      </hudson.plugins.git.GitSCM>
             +    </scms>
             @@ @@
-            -  <assignedNode>fake</assignedNode>'''
+            -  <assignedNode>fake</assignedNode>"""
         )
 
         # Test git -> additional
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 git:
                   branch: custom_main
 
@@ -1251,15 +1220,15 @@ def testGitAndAdditionalRepositories(self):
                 - git:
                     url: http://additional.git
                     branch: custom_additional
-                '''
+                """
             ),
-            expected_diff=expected_diff
+            expected_diff=expected_diff,
         )
 
         # Test additional -> git
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 additional_repositories:
                 - git:
                     url: http://additional.git
@@ -1267,40 +1236,38 @@ def testGitAndAdditionalRepositories(self):
 
                 git:
                   branch: custom_main
-                '''
+                """
             ),
-            expected_diff=expected_diff
+            expected_diff=expected_diff,
         )
 
-
     def testUnknownGitOptions(self):
         with pytest.raises(RuntimeError) as e:
             self._DoTest(
                 yaml_contents=dedent(
-                    '''
+                    """
                     git:
                       unknown: ""
-                    '''
+                    """
                 ),
-                expected_diff=''
+                expected_diff="",
             )
         assert str(e.value) == "Received unknown git options: ['unknown']"
 
-
     def testGitOptions(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 git:
                   recursive_submodules: true
                   reference: "/home/reference.git"
                   target_dir: "main_application"
                   timeout: 30
                   clean_checkout: false
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 -    <relativeTargetDir>fake</relativeTargetDir>
                 +    <relativeTargetDir>main_application</relativeTargetDir>
@@ -1311,25 +1278,23 @@ def testGitOptions(self):
                 +      <hudson.plugins.git.extensions.impl.CloneOption>
                 +        <reference>/home/reference.git</reference>
                 +        <timeout>30</timeout>
-                +      </hudson.plugins.git.extensions.impl.CloneOption>'''
+                +      </hudson.plugins.git.extensions.impl.CloneOption>"""
             ),
         )
 
-
-
     def testEmailNotificationDict(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 email_notification:
                   recipients: user@company.com other@company.com
                   notify_every_build: true
                   notify_individuals: true
 
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <hudson.tasks.Mailer>
@@ -1337,19 +1302,19 @@ def testEmailNotificationDict(self):
                 +      <dontNotifyEveryUnstableBuild>false</dontNotifyEveryUnstableBuild>
                 +      <sendToIndividuals>true</sendToIndividuals>
                 +    </hudson.tasks.Mailer>
-                +  </publishers>'''
+                +  </publishers>"""
             ),
         )
 
     def testEmailNotificationString(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 email_notification: user@company.com other@company.com
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <hudson.tasks.Mailer>
@@ -1357,19 +1322,19 @@ def testEmailNotificationString(self):
                 +      <dontNotifyEveryUnstableBuild>true</dontNotifyEveryUnstableBuild>
                 +      <sendToIndividuals>false</sendToIndividuals>
                 +    </hudson.tasks.Mailer>
-                +  </publishers>'''
+                +  </publishers>"""
             ),
         )
 
     def testEmailNotificationWithTests(self):
-        '''
+        """
         When we have both email_notification, and some test pattern, we have to make sure that the
         output jenkins job xml places the email_notification publisher AFTER the test publisher,
         otherwise builds with failed tests might be reported as successful via email
-        '''
+        """
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 email_notification:
                   recipients: user@company.com other@company.com
                   notify_every_build: true
@@ -1377,10 +1342,10 @@ def testEmailNotificationWithTests(self):
 
                 jsunit_patterns:
                 - "jsunit*.xml"
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <xunit>
@@ -1416,23 +1381,22 @@ def testEmailNotificationWithTests(self):
                 +        </hudson.plugins.ws__cleanup.Pattern>
                 +      </patterns>
                 +    </hudson.plugins.ws__cleanup.PreBuildCleanup>
-                +  </buildWrappers>'''
+                +  </buildWrappers>"""
             ),
         )
 
-
     def testNotification(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 notification:
                   protocol: ALPHA
                   format: BRAVO
                   url: https://bravo
-                '''
+                """
             ),
             expected_diff=dedent(
-            '''\
+                """\
             @@ @@
             +  <properties>
             +    <com.tikal.hudson.plugins.notification.HudsonNotificationProperty plugin="notification@1.9">
@@ -1447,24 +1411,23 @@ def testNotification(self):
             +        </com.tikal.hudson.plugins.notification.Endpoint>
             +      </endpoints>
             +    </com.tikal.hudson.plugins.notification.HudsonNotificationProperty>
-            +  </properties>'''
-            )
+            +  </properties>"""
+            ),
         ),
 
-
     def testSlack(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 slack:
                   team: esss
                   room: zulu
                   token: ALPHA
                   url: https://bravo
-                '''
+                """
             ),
             expected_diff=dedent(
-            '''\
+                """\
             @@ @@
             +  <properties>
             +    <jenkins.plugins.slack.SlackNotifier_-SlackJobProperty plugin="slack@1.2">
@@ -1485,65 +1448,70 @@ def testSlack(self):
             +      <buildServerUrl>https://bravo</buildServerUrl>
             +      <room>#zulu</room>
             +    </jenkins.plugins.slack.SlackNotifier>
-            +  </publishers>'''
-            )
+            +  </publishers>"""
+            ),
         ),
 
-    @pytest.mark.parametrize('conf_value, expected_name', [
-        ('', 'xterm'),
-        ('xterm', 'xterm'),
-        ('vga', 'vga'),
-    ])
+    @pytest.mark.parametrize(
+        "conf_value, expected_name",
+        [
+            ("", "xterm"),
+            ("xterm", "xterm"),
+            ("vga", "vga"),
+        ],
+    )
     def testAnsiColor(self, conf_value, expected_name):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 console_color: %s
-                '''
-            ) % (conf_value,),
+                """
+            )
+            % (conf_value,),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <buildWrappers>
                 +    <hudson.plugins.ansicolor.AnsiColorBuildWrapper plugin="ansicolor@0.4.2">
                 +      <colorMapName>%s</colorMapName>
                 +    </hudson.plugins.ansicolor.AnsiColorBuildWrapper>
-                +  </buildWrappers>'''
-            ) % (expected_name,)
+                +  </buildWrappers>"""
+            )
+            % (expected_name,),
         )
 
-
     def testTimestamps(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 timestamps:
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <buildWrappers>
                 +    <hudson.plugins.timestamper.TimestamperBuildWrapper plugin="timestamper@1.7.4"/>
-                +  </buildWrappers>'''
-            )
+                +  </buildWrappers>"""
+            ),
         )
 
-
-    @pytest.mark.parametrize('condition', ('SUCCESS', 'UNSTABLE', 'FAILED', 'ALWAYS'))
+    @pytest.mark.parametrize("condition", ("SUCCESS", "UNSTABLE", "FAILED", "ALWAYS"))
     def testTriggerJobNoParameters(self, condition):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 trigger_jobs:
                   names:
                     - etk-master-linux64-27
                     - etk-master-linux64-36
                   condition: {condition}
-                '''.format(condition=condition)
+                """.format(
+                    condition=condition
+                )
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <hudson.plugins.parameterizedtrigger.BuildTrigger plugin="parameterized-trigger@2.33">
@@ -1557,30 +1525,34 @@ def testTriggerJobNoParameters(self, condition):
                 +        </hudson.plugins.parameterizedtrigger.BuildTriggerConfig>
                 +      </configs>
                 +    </hudson.plugins.parameterizedtrigger.BuildTrigger>
-                +  </publishers>'''.format(condition=condition)
-            )
+                +  </publishers>""".format(
+                    condition=condition
+                )
+            ),
         )
 
-
     def testTriggerJobInvalidCondition(self):
-        with pytest.raises(RuntimeError, match=r"Invalid value for condition: u?'UNKNOWN', expected one of .*"):
+        with pytest.raises(
+            RuntimeError,
+            match=r"Invalid value for condition: u?'UNKNOWN', expected one of .*",
+        ):
             self._DoTest(
                 yaml_contents=dedent(
-                    '''
+                    """
                     trigger_jobs:
                       names:
                         - etk-master-linux64-27
                         - etk-master-linux64-36
                       condition: UNKNOWN
-                    '''
+                    """
                 ),
-                expected_diff='',
+                expected_diff="",
             )
 
     def testTriggerJobParameters(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 trigger_jobs:
                   names:
                     - etk-master-linux64-27
@@ -1588,10 +1560,10 @@ def testTriggerJobParameters(self):
                   parameters:
                     - KEY1=VALUE1
                     - KEY2=VALUE2
-                '''
+                """
             ),
             expected_diff=dedent(
-                '''\
+                """\
                 @@ @@
                 +  <publishers>
                 +    <hudson.plugins.parameterizedtrigger.BuildTrigger plugin="parameterized-trigger@2.33">
@@ -1609,29 +1581,30 @@ def testTriggerJobParameters(self):
                 +        </hudson.plugins.parameterizedtrigger.BuildTriggerConfig>
                 +      </configs>
                 +    </hudson.plugins.parameterizedtrigger.BuildTrigger>
-                +  </publishers>'''
-            )
+                +  </publishers>"""
+            ),
         )
 
-
     def testAnsiColorUnknowOption(self):
-        with pytest.raises(RuntimeError, match='Received unknown console_color option.'):
+        with pytest.raises(
+            RuntimeError, match="Received unknown console_color option."
+        ):
             self._DoTest(
                 yaml_contents=dedent(
-                    '''
+                    """
                     console_color: unknown-value
-                    '''
+                    """
                 ),
-                expected_diff='',
+                expected_diff="",
             )
 
     @pytest.mark.parametrize(
-        'scenario',
+        "scenario",
         [
-            'complete',
-            'incomplete_metrics',
-            'incomplete_values',
-        ]
+            "complete",
+            "incomplete_metrics",
+            "incomplete_values",
+        ],
     )
     def testCoverage(self, scenario):
         # Expected defaults
@@ -1647,7 +1620,7 @@ def testCoverage(self, scenario):
         failing_line = 0
         failing_conditional = 0
 
-        if scenario == 'complete':
+        if scenario == "complete":
             healthy_method = 100
             healthy_line = 100
             healthy_conditional = 90
@@ -1660,7 +1633,7 @@ def testCoverage(self, scenario):
             failing_line = 60
             failing_conditional = 50
             contents = dedent(
-                r'''
+                r"""
                 coverage:
                   report_pattern: "**/build/coverage/*.xml"
                   healthy:
@@ -1675,40 +1648,40 @@ def testCoverage(self, scenario):
                     method: {failing_method}
                     line: {failing_line}
                     conditional: {failing_conditional}
-                '''
+                """
             )
-        elif scenario == 'incomplete_metrics':
+        elif scenario == "incomplete_metrics":
             healthy_method = 100
             healthy_line = 100
             healthy_conditional = 90
 
             contents = dedent(
-                r'''
+                r"""
                 coverage:
                   report_pattern: "**/build/coverage/*.xml"
                   healthy:
                     method: {healthy_method}
                     line: {healthy_line}
                     conditional: {healthy_conditional}
-                '''
+                """
             )
         else:
-            assert scenario == 'incomplete_values', "Unknown scenario"
+            assert scenario == "incomplete_values", "Unknown scenario"
             healthy_method = 100
             healthy_line = 100
 
             contents = dedent(
-                r'''
+                r"""
                 coverage:
                   report_pattern: "**/build/coverage/*.xml"
                   healthy:
                     method: {healthy_method}
                     line: {healthy_line}
-                '''
+                """
             )
 
         expected_diff = dedent(
-                '''\
+            """\
                 @@ @@
                 +  <publishers>
                 +    <hudson.plugins.cobertura.CoberturaPublisher plugin="cobertura@1.9.7">
@@ -1770,8 +1743,8 @@ def testCoverage(self, scenario):
                 +        </targets>
                 +      </failingTarget>
                 +    </hudson.plugins.cobertura.CoberturaPublisher>
-                +  </publishers>'''
-            )
+                +  </publishers>"""
+        )
 
         contents_ = contents.format(**locals())
 
@@ -1792,21 +1765,24 @@ def testCoverage(self, scenario):
         )
 
     def testCoverageFailWhenMissingReportPattern(self):
-        with pytest.raises(ValueError, match='Report pattern is required by coverage') as e:
-            self._GenerateJob(yaml_contents=dedent(
-                r'''
+        with pytest.raises(
+            ValueError, match="Report pattern is required by coverage"
+        ) as e:
+            self._GenerateJob(
+                yaml_contents=dedent(
+                    r"""
                 coverage:
                   healthy:
                     method: 100
                     line: 100
-                '''
-            ))
-
+                """
+                )
+            )
 
     def testWarnings(self):
         self._DoTest(
             yaml_contents=dedent(
-                '''
+                """
                 warnings:
                   console:
                     - parser: Clang (LLVM based)
@@ -1816,10 +1792,13 @@ def testWarnings(self):
                       file_pattern: "*.cpplint"
                     - parser: CodeAnalysis
                       file_pattern: "*.codeanalysis"
-                '''
+                """
             ),
-            expected_diff='@@ @@\n' + '\n'.join('+  ' + s for s in dedent(
-            '''\
+            expected_diff="@@ @@\n"
+            + "\n".join(
+                "+  " + s
+                for s in dedent(
+                    """\
             <publishers>
               <hudson.plugins.warnings.WarningsPublisher plugin="warnings@4.59">
                 <healthy/>
@@ -1874,32 +1853,36 @@ def testWarnings(self):
                   </hudson.plugins.warnings.ConsoleParser>
                 </consoleParsers>
               </hudson.plugins.warnings.WarningsPublisher>
-            </publishers>''').splitlines()),
+            </publishers>"""
+                ).splitlines()
+            ),
         )
 
-
     def testWarningsEmpty(self):
         with pytest.raises(JobsDoneFileTypeError):
-            self._GenerateJob(yaml_contents='warnings:')
+            self._GenerateJob(yaml_contents="warnings:")
         with pytest.raises(ValueError, match="Empty 'warnings' options.*"):
-            self._GenerateJob(yaml_contents='warnings: {}')
-
+            self._GenerateJob(yaml_contents="warnings: {}")
 
     def testWarningsWrongOption(self):
-        with pytest.raises(ValueError, match="Received unknown 'warnings' options: zucchini."):
-            self._GenerateJob(yaml_contents=dedent('''\
+        with pytest.raises(
+            ValueError, match="Received unknown 'warnings' options: zucchini."
+        ):
+            self._GenerateJob(
+                yaml_contents=dedent(
+                    """\
                 warnings:
                   zucchini:
                     - parser: Pizza
                   file:
                     - parser: CppLint
                       file_pattern: "*.cpplint"
-                ''')
+                """
+                )
             )
 
-
     def _GenerateJob(self, yaml_contents):
-        repository = Repository(url='http://fake.git', branch='not_master')
+        repository = Repository(url="http://fake.git", branch="not_master")
         jobs_done_jobs = JobsDoneJob.CreateFromYAML(yaml_contents, repository)
 
         job_generator = JenkinsXmlJobGenerator()
@@ -1907,25 +1890,26 @@ def _GenerateJob(self, yaml_contents):
         return job_generator.GetJob()
 
     def _DoTest(self, yaml_contents, expected_diff):
-        '''
+        """
         :param unicode yaml_contents:
             Contents of JobsDoneJob used for this test
 
         :param unicode expected_diff:
             Expected diff from build jobs from `yaml_contents`, when compared to BASIC_EXPECTED_XML.
-        '''
+        """
         jenkins_job = self._GenerateJob(yaml_contents=yaml_contents)
         self._AssertDiff(jenkins_job.xml, expected_diff)
 
-
     def _AssertDiff(self, obtained_xml, expected_diff):
-        diff = ''.join(difflib.unified_diff(
-            self.BASIC_EXPECTED_XML.splitlines(1),
-            str(obtained_xml).splitlines(1),
-            n=0,
-        ))
-        diff = '\n'.join(diff.splitlines()[2:])
-        diff = re.sub('@@.*@@', '@@ @@', diff, flags=re.MULTILINE)
+        diff = "".join(
+            difflib.unified_diff(
+                self.BASIC_EXPECTED_XML.splitlines(1),
+                str(obtained_xml).splitlines(1),
+                n=0,
+            )
+        )
+        diff = "\n".join(diff.splitlines()[2:])
+        diff = re.sub("@@.*@@", "@@ @@", diff, flags=re.MULTILINE)
 
         print(obtained_xml)
 
@@ -1933,17 +1917,16 @@ def _AssertDiff(self, obtained_xml, expected_diff):
         assert expected_diff == diff
 
 
-
-#===================================================================================================
+# ===================================================================================================
 # TestJenkinsActions
-#===================================================================================================
+# ===================================================================================================
 class TestJenkinsActions(object):
-    '''
+    """
     Integration tests for Jenkins actions
-    '''
+    """
 
     _JOBS_DONE_FILE_CONTENTS = dedent(
-        '''
+        """
         junit_patterns:
         - "junit*.xml"
 
@@ -1967,29 +1950,28 @@ class TestJenkinsActions(object):
             - venus
             - jupiter
 
-        '''
+        """
     )
 
-    _REPOSITORY = Repository(url='http://space.git', branch='branch')
+    _REPOSITORY = Repository(url="http://space.git", branch="branch")
 
     def testGetJobsFromFile(self):
         jobs = GetJobsFromFile(self._REPOSITORY, self._JOBS_DONE_FILE_CONTENTS)
         assert len(jobs) == 3
 
-
     def testGetJobsFromDirectory(self, tmpdir):
-        repo_path = tmpdir / 'git_repository'
+        repo_path = tmpdir / "git_repository"
         repo_path.mkdir()
 
         # Prepare git repository
         with repo_path.as_cwd():
-            check_call('git init', shell=True)
-            check_call('git config user.name Bob', shell=True)
-            check_call('git config user.email bob@example.com', shell=True)
-            check_call('git remote add origin %s' % self._REPOSITORY.url, shell=True)
-            check_call('git checkout -b %s' % self._REPOSITORY.branch, shell=True)
-            repo_path.join('.gitignore').write('')
-            check_call('git add .', shell=True)
+            check_call("git init", shell=True)
+            check_call("git config user.name Bob", shell=True)
+            check_call("git config user.email bob@example.com", shell=True)
+            check_call("git remote add origin %s" % self._REPOSITORY.url, shell=True)
+            check_call("git checkout -b %s" % self._REPOSITORY.branch, shell=True)
+            repo_path.join(".gitignore").write("")
+            check_call("git add .", shell=True)
             check_call('git commit -a -m "First commit"', shell=True)
 
             # If there is no jobs_done file, we should get zero jobs
@@ -1998,101 +1980,123 @@ def testGetJobsFromDirectory(self, tmpdir):
 
             # Create jobs_done file
             repo_path.join(JOBS_DONE_FILENAME).write(self._JOBS_DONE_FILE_CONTENTS)
-            check_call('git add .', shell=True)
+            check_call("git add .", shell=True)
             check_call('git commit -a -m "Added jobs_done file"', shell=True)
 
             _repository, jobs = GetJobsFromDirectory(str(repo_path))
             assert len(jobs) == 3
 
-
     def testUploadJobsFromFile(self, monkeypatch):
-        '''
+        """
         Tests that UploadJobsFromFile correctly calls JenkinsJobPublisher (already tested elsewhere)
-        '''
+        """
+
         def MockPublishToUrl(self, url, username, password):
-            assert url == 'jenkins_url'
-            assert username == 'jenkins_user'
-            assert password == 'jenkins_pass'
+            assert url == "jenkins_url"
+            assert username == "jenkins_user"
+            assert password == "jenkins_pass"
 
-            assert set(self.jobs.keys()) == {'space-branch-venus', 'space-branch-jupiter', 'space-branch-mercury'}
+            assert set(self.jobs.keys()) == {
+                "space-branch-venus",
+                "space-branch-jupiter",
+                "space-branch-mercury",
+            }
 
-            return 'mock publish result'
+            return "mock publish result"
 
-        monkeypatch.setattr(JenkinsJobPublisher, 'PublishToUrl', MockPublishToUrl)
+        monkeypatch.setattr(JenkinsJobPublisher, "PublishToUrl", MockPublishToUrl)
 
         result = UploadJobsFromFile(
             repository=self._REPOSITORY,
             jobs_done_file_contents=self._JOBS_DONE_FILE_CONTENTS,
-            url='jenkins_url',
-            username='jenkins_user',
-            password='jenkins_pass',
+            url="jenkins_url",
+            username="jenkins_user",
+            password="jenkins_pass",
         )
-        assert result == 'mock publish result'
-
+        assert result == "mock publish result"
 
 
-#===================================================================================================
+# ===================================================================================================
 # TestJenkinsPublisher
-#===================================================================================================
+# ===================================================================================================
 class TestJenkinsPublisher(object):
-
     def testPublishToDirectory(self, tmpdir):
         self._GetPublisher().PublishToDirectory(str(tmpdir))
 
         assert set(os.path.basename(str(x)) for x in tmpdir.listdir()) == {
-            'space-milky_way-jupiter', 'space-milky_way-mercury', 'space-milky_way-venus'}
-
-        assert tmpdir.join('space-milky_way-jupiter').read() == 'jupiter'
-        assert tmpdir.join('space-milky_way-mercury') .read() == 'mercury'
-        assert tmpdir.join('space-milky_way-venus').read() == 'venus'
+            "space-milky_way-jupiter",
+            "space-milky_way-mercury",
+            "space-milky_way-venus",
+        }
 
+        assert tmpdir.join("space-milky_way-jupiter").read() == "jupiter"
+        assert tmpdir.join("space-milky_way-mercury").read() == "mercury"
+        assert tmpdir.join("space-milky_way-venus").read() == "venus"
 
     def testPublishToUrl(self, monkeypatch):
         mock_jenkins = self._MockJenkinsAPI(monkeypatch)
 
         new_jobs, updated_jobs, deleted_jobs = self._GetPublisher().PublishToUrl(
-            url='jenkins_url',
-            username='jenkins_user',
-            password='jenkins_pass',
+            url="jenkins_url",
+            username="jenkins_user",
+            password="jenkins_pass",
+        )
+        assert (
+            set(new_jobs)
+            == mock_jenkins.NEW_JOBS
+            == {"space-milky_way-venus", "space-milky_way-jupiter"}
+        )
+        assert (
+            set(updated_jobs)
+            == mock_jenkins.UPDATED_JOBS
+            == {"space-milky_way-mercury"}
+        )
+        assert (
+            set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {"space-milky_way-saturn"}
         )
-        assert set(new_jobs) == mock_jenkins.NEW_JOBS == {'space-milky_way-venus', 'space-milky_way-jupiter'}
-        assert set(updated_jobs) == mock_jenkins.UPDATED_JOBS == {'space-milky_way-mercury'}
-        assert set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {'space-milky_way-saturn'}
-
 
     def testPublishToUrlProxyErrorOnce(self, monkeypatch):
         # Do not actually sleep during tests
-        monkeypatch.setattr(JenkinsJobPublisher, 'RETRY_SLEEP', 0)
+        monkeypatch.setattr(JenkinsJobPublisher, "RETRY_SLEEP", 0)
 
         # Tell mock jenkins to raise a proxy error, our retry should catch it and continue
         mock_jenkins = self._MockJenkinsAPI(monkeypatch, proxy_errors=1)
         new_jobs, updated_jobs, deleted_jobs = self._GetPublisher().PublishToUrl(
-            url='jenkins_url',
-            username='jenkins_user',
-            password='jenkins_pass',
+            url="jenkins_url",
+            username="jenkins_user",
+            password="jenkins_pass",
+        )
+        assert (
+            set(new_jobs)
+            == mock_jenkins.NEW_JOBS
+            == {"space-milky_way-venus", "space-milky_way-jupiter"}
+        )
+        assert (
+            set(updated_jobs)
+            == mock_jenkins.UPDATED_JOBS
+            == {"space-milky_way-mercury"}
+        )
+        assert (
+            set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {"space-milky_way-saturn"}
         )
-        assert set(new_jobs) == mock_jenkins.NEW_JOBS == {'space-milky_way-venus', 'space-milky_way-jupiter'}
-        assert set(updated_jobs) == mock_jenkins.UPDATED_JOBS == {'space-milky_way-mercury'}
-        assert set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {'space-milky_way-saturn'}
-
 
     def testPublishToUrlProxyErrorTooManyTimes(self, monkeypatch):
         # Do not actually sleep during tests
-        monkeypatch.setattr(JenkinsJobPublisher, 'RETRY_SLEEP', 0)
-        monkeypatch.setattr(JenkinsJobPublisher, 'RETRIES', 3)
+        monkeypatch.setattr(JenkinsJobPublisher, "RETRY_SLEEP", 0)
+        monkeypatch.setattr(JenkinsJobPublisher, "RETRIES", 3)
 
         # Tell mock jenkins to raise 5 proxy errors in a row, this should bust our retry limit
         self._MockJenkinsAPI(monkeypatch, proxy_errors=5)
 
         from requests.exceptions import HTTPError
+
         with pytest.raises(HTTPError):
             self._GetPublisher().PublishToUrl(
-                url='jenkins_url',
-                username='jenkins_user',
-                password='jenkins_pass',
+                url="jenkins_url",
+                username="jenkins_user",
+                password="jenkins_pass",
             )
 
-
     def testPublishToUrl2(self, monkeypatch):
         mock_jenkins = self._MockJenkinsAPI(monkeypatch)
 
@@ -2100,26 +2104,34 @@ def testPublishToUrl2(self, monkeypatch):
         publisher.jobs = {}
 
         new_jobs, updated_jobs, deleted_jobs = publisher.PublishToUrl(
-            url='jenkins_url',
-            username='jenkins_user',
-            password='jenkins_pass',
+            url="jenkins_url",
+            username="jenkins_user",
+            password="jenkins_pass",
         )
         assert set(new_jobs) == mock_jenkins.NEW_JOBS == set()
         assert set(updated_jobs) == mock_jenkins.UPDATED_JOBS == set()
-        assert set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {'space-milky_way-mercury', 'space-milky_way-saturn'}
-
+        assert (
+            set(deleted_jobs)
+            == mock_jenkins.DELETED_JOBS
+            == {"space-milky_way-mercury", "space-milky_way-saturn"}
+        )
 
     def _GetPublisher(self):
-        repository = Repository(url='http://server/space.git', branch='milky_way')
+        repository = Repository(url="http://server/space.git", branch="milky_way")
         jobs = [
-            JenkinsJob(name='space-milky_way-jupiter', xml='jupiter', repository=repository),
-            JenkinsJob(name='space-milky_way-mercury', xml='mercury', repository=repository),
-            JenkinsJob(name='space-milky_way-venus', xml='venus', repository=repository),
+            JenkinsJob(
+                name="space-milky_way-jupiter", xml="jupiter", repository=repository
+            ),
+            JenkinsJob(
+                name="space-milky_way-mercury", xml="mercury", repository=repository
+            ),
+            JenkinsJob(
+                name="space-milky_way-venus", xml="venus", repository=repository
+            ),
         ]
 
         return JenkinsJobPublisher(repository, jobs)
 
-
     def _MockJenkinsAPI(self, monkeypatch, proxy_errors=0):
         class MockJenkins(object):
             NEW_JOBS = set()
@@ -2127,19 +2139,22 @@ class MockJenkins(object):
             DELETED_JOBS = set()
 
             def __init__(self, url, username, password):
-                assert url == 'jenkins_url'
-                assert username == 'jenkins_user'
-                assert password == 'jenkins_pass'
+                assert url == "jenkins_url"
+                assert username == "jenkins_user"
+                assert password == "jenkins_pass"
                 self.proxy_errors_raised = 0
 
             def get_jobs(self):
-                return [{'name': 'space-milky_way-mercury'}, {'name': 'space-milky_way-saturn'}]
+                return [
+                    {"name": "space-milky_way-mercury"},
+                    {"name": "space-milky_way-saturn"},
+                ]
 
             def get_job_config(self, job_name):
                 # Test with single, and multiple scms
-                if job_name == 'space-milky_way-mercury':
+                if job_name == "space-milky_way-mercury":
                     return dedent(
-                        '''
+                        """
                         <project>
                           <scm>
                             <userRemoteConfigs>
@@ -2156,11 +2171,11 @@ def get_job_config(self, job_name):
                             </branches>
                           </scm>
                         </project>
-                        '''
+                        """
                     )
-                elif job_name == 'space-milky_way-saturn':
+                elif job_name == "space-milky_way-saturn":
                     return dedent(
-                        '''
+                        """
                         <project>
                           <scm>
                             <scms>
@@ -2199,10 +2214,10 @@ def get_job_config(self, job_name):
                             </scms>
                           </scm>
                         </project>
-                        '''
+                        """
                     )
                 else:
-                    assert 0, f'unknown job name: {job_name}'
+                    assert 0, f"unknown job name: {job_name}"
 
             def create_job(self, name, xml):
                 assert type(xml) is str
@@ -2216,13 +2231,13 @@ def delete_job(self, name):
                     self.proxy_errors_raised += 1
                     from unittest.mock import Mock
                     from requests.exceptions import HTTPError
+
                     response = Mock()
                     response.status_code = 403
                     raise HTTPError(response=response)
 
                 self.DELETED_JOBS.add(name)
 
-
-        monkeypatch.setattr(jenkins, 'Jenkins', MockJenkins)
+        monkeypatch.setattr(jenkins, "Jenkins", MockJenkins)
 
         return MockJenkins
diff --git a/src/jobs_done10/server/_tests/test_server.py b/src/jobs_done10/server/_tests/test_server.py
index 1753ff4..18156d3 100644
--- a/src/jobs_done10/server/_tests/test_server.py
+++ b/src/jobs_done10/server/_tests/test_server.py
@@ -2,25 +2,26 @@
 
 import pytest
 import requests_mock
+
 from jobs_done10.repository import Repository
 
 
-@pytest.fixture(name='client')
+@pytest.fixture(name="client")
 def client_(monkeypatch, tmpdir):
     from jobs_done10.server.app import app
 
     test_env = {
-        'JD_JENKINS_URL': 'https://example.com/jenkins',
-        'JD_JENKINS_USERNAME': 'jenkins_user',
-        'JD_JENKINS_PASSWORD': 'jenkins_password',
-        'JD_STASH_URL': 'https://example.com/stash',
-        'JD_STASH_USERNAME': 'stash_user',
-        'JD_STASH_PASSWORD': 'stash_password',
-        'JD_EMAIL_SERVER': 'smtp.example.com',
-        'JD_EMAIL_FROM': 'JobsDone Bot <jobsdone@example.com>',
-        'JD_EMAIL_PORT': '5900',
-        'JD_EMAIL_USER': 'email_user',
-        'JD_EMAIL_PASSWORD': 'email_password',
+        "JD_JENKINS_URL": "https://example.com/jenkins",
+        "JD_JENKINS_USERNAME": "jenkins_user",
+        "JD_JENKINS_PASSWORD": "jenkins_password",
+        "JD_STASH_URL": "https://example.com/stash",
+        "JD_STASH_USERNAME": "stash_user",
+        "JD_STASH_PASSWORD": "stash_password",
+        "JD_EMAIL_SERVER": "smtp.example.com",
+        "JD_EMAIL_FROM": "JobsDone Bot <jobsdone@example.com>",
+        "JD_EMAIL_PORT": "5900",
+        "JD_EMAIL_USER": "email_user",
+        "JD_EMAIL_PASSWORD": "email_password",
     }
     for env_var, value in test_env.items():
         monkeypatch.setenv(env_var, value)
@@ -28,7 +29,7 @@ def client_(monkeypatch, tmpdir):
     return app.test_client()
 
 
-@pytest.fixture(name='post_json_data')
+@pytest.fixture(name="post_json_data")
 def post_json_data_():
     """
     Return the json data which posted by Stash/BitBucket Server. Obtained from the webhooks page configuration:
@@ -37,111 +38,182 @@ def post_json_data_():
 
     There's a "View details" link which you can inspect the body of the post.
     """
-    return {"eventKey": "repo:refs_changed", "date": "2018-06-18T16:20:06-0300",
-            "actor": {"name": "jenkins", "emailAddress": "bugreport+jenkins@esss.co", "id": 2852,
-                      "displayName": "jenkins", "active": True, "slug": "jenkins", "type": "NORMAL"},
-            "repository": {"slug": "eden", "id": 2231, "name": "eden", "scmId": "git", "state": "AVAILABLE",
-                           "statusMessage": "Available", "forkable": True,
-                           "project": {"key": "ESSS", "id": 1, "name": "ESSS", "description": "Dev projects",
-                                       "public": False, "type": "NORMAL"}, "public": False}, "changes": [
-            {"ref": {"id": "refs/heads/stable-pwda11-master", "displayId": "stable-pwda11-master", "type": "BRANCH"},
-             "refId": "refs/heads/stable-pwda11-master", "fromHash": "cd39f701ae0a729b73c57b7848fbd1f340a36514",
-             "toHash": "8522b06a7c330008814a522d0342be9a997a1460", "type": "UPDATE"}]}
-
-
-@pytest.fixture(name='repo_info_json_data')
+    return {
+        "eventKey": "repo:refs_changed",
+        "date": "2018-06-18T16:20:06-0300",
+        "actor": {
+            "name": "jenkins",
+            "emailAddress": "bugreport+jenkins@esss.co",
+            "id": 2852,
+            "displayName": "jenkins",
+            "active": True,
+            "slug": "jenkins",
+            "type": "NORMAL",
+        },
+        "repository": {
+            "slug": "eden",
+            "id": 2231,
+            "name": "eden",
+            "scmId": "git",
+            "state": "AVAILABLE",
+            "statusMessage": "Available",
+            "forkable": True,
+            "project": {
+                "key": "ESSS",
+                "id": 1,
+                "name": "ESSS",
+                "description": "Dev projects",
+                "public": False,
+                "type": "NORMAL",
+            },
+            "public": False,
+        },
+        "changes": [
+            {
+                "ref": {
+                    "id": "refs/heads/stable-pwda11-master",
+                    "displayId": "stable-pwda11-master",
+                    "type": "BRANCH",
+                },
+                "refId": "refs/heads/stable-pwda11-master",
+                "fromHash": "cd39f701ae0a729b73c57b7848fbd1f340a36514",
+                "toHash": "8522b06a7c330008814a522d0342be9a997a1460",
+                "type": "UPDATE",
+            }
+        ],
+    }
+
+
+@pytest.fixture(name="repo_info_json_data")
 def repo_info_json_data_():
     """
     Return json data that results from a call to the project/slug endpoint.
 
     Taken from manually doing the query on our live server.
     """
-    return {'forkable': True,
-            'id': 2231,
-            'links': {'clone': [{'href': 'https://bruno@eden.esss.com.br/stash/scm/esss/eden.git',
-                                 'name': 'http'},
-                                {'href': 'ssh://git@eden.fln.esss.com.br:7999/esss/eden.git',
-                                 'name': 'ssh'}],
-                      'self': [{'href': 'https://eden.esss.com.br/stash/projects/ESSS/repos/eden/browse'}]},
-            'name': 'eden',
-            'project': {'description': 'Dev projects',
-                        'id': 1,
-                        'key': 'ESSS',
-                        'links': {'self': [{'href': 'https://eden.esss.com.br/stash/projects/ESSS'}]},
-                        'name': 'ESSS',
-                        'public': False,
-                        'type': 'NORMAL'},
-            'public': False,
-            'scmId': 'git',
-            'slug': 'eden',
-            'state': 'AVAILABLE',
-            'statusMessage': 'Available'}
+    return {
+        "forkable": True,
+        "id": 2231,
+        "links": {
+            "clone": [
+                {
+                    "href": "https://bruno@eden.esss.com.br/stash/scm/esss/eden.git",
+                    "name": "http",
+                },
+                {
+                    "href": "ssh://git@eden.fln.esss.com.br:7999/esss/eden.git",
+                    "name": "ssh",
+                },
+            ],
+            "self": [
+                {
+                    "href": "https://eden.esss.com.br/stash/projects/ESSS/repos/eden/browse"
+                }
+            ],
+        },
+        "name": "eden",
+        "project": {
+            "description": "Dev projects",
+            "id": 1,
+            "key": "ESSS",
+            "links": {
+                "self": [{"href": "https://eden.esss.com.br/stash/projects/ESSS"}]
+            },
+            "name": "ESSS",
+            "public": False,
+            "type": "NORMAL",
+        },
+        "public": False,
+        "scmId": "git",
+        "slug": "eden",
+        "state": "AVAILABLE",
+        "statusMessage": "Available",
+    }
 
 
 def test_post(client, post_json_data, mocker, repo_info_json_data):
-    contents = 'jobs_done yaml contents'
+    contents = "jobs_done yaml contents"
 
-    new_jobs = ['new1-eden-master', 'new2-eden-master']
-    updated_jobs = ['upd1-eden-master', 'upd2-eden-master']
-    deleted_jobs = ['del1-eden-master', 'del2-eden-master']
+    new_jobs = ["new1-eden-master", "new2-eden-master"]
+    updated_jobs = ["upd1-eden-master", "upd2-eden-master"]
+    deleted_jobs = ["del1-eden-master", "del2-eden-master"]
 
-    upload_mock = mocker.patch('jobs_done10.generators.jenkins.UploadJobsFromFile', autospec=True,
-                               return_value=(new_jobs, updated_jobs, deleted_jobs))
+    upload_mock = mocker.patch(
+        "jobs_done10.generators.jenkins.UploadJobsFromFile",
+        autospec=True,
+        return_value=(new_jobs, updated_jobs, deleted_jobs),
+    )
 
     with requests_mock.Mocker() as m:
-        stash_url = 'https://example.com/stash'
-        project_key = 'ESSS'
-        slug = 'eden'
-        path = '.jobs_done.yaml'
-        ref = '8522b06a7c330008814a522d0342be9a997a1460'
-        m.get(f'{stash_url}/projects/{project_key}/repos/{slug}/raw/{path}?at={ref}', text=contents)
-
-        m.get(f'{stash_url}/rest/api/1.0/projects/{project_key}/repos/{slug}', json=repo_info_json_data)
+        stash_url = "https://example.com/stash"
+        project_key = "ESSS"
+        slug = "eden"
+        path = ".jobs_done.yaml"
+        ref = "8522b06a7c330008814a522d0342be9a997a1460"
+        m.get(
+            f"{stash_url}/projects/{project_key}/repos/{slug}/raw/{path}?at={ref}",
+            text=contents,
+        )
+
+        m.get(
+            f"{stash_url}/rest/api/1.0/projects/{project_key}/repos/{slug}",
+            json=repo_info_json_data,
+        )
 
         response = client.post(json=post_json_data)
         assert response.status_code == 200
-        assert response.mimetype == 'text/html'
-        assert response.data.decode('UTF-8') == dedent("""
+        assert response.mimetype == "text/html"
+        assert (
+            response.data.decode("UTF-8")
+            == dedent(
+                """
             NEW - new1-eden-master
             NEW - new2-eden-master
             UPD - upd1-eden-master
             UPD - upd2-eden-master
             DEL - del1-eden-master
             DEL - del2-eden-master
-        """).strip()
+        """
+            ).strip()
+        )
 
     assert upload_mock.call_count == 1
     args, kwargs = upload_mock.call_args
     assert args == ()
     assert kwargs == dict(
-        repository=Repository('ssh://git@eden.fln.esss.com.br:7999/esss/eden.git', 'stable-pwda11-master'),
+        repository=Repository(
+            "ssh://git@eden.fln.esss.com.br:7999/esss/eden.git", "stable-pwda11-master"
+        ),
         jobs_done_file_contents=contents,
-        url='https://example.com/jenkins',
-        username='jenkins_user',
-        password='jenkins_password',
+        url="https://example.com/jenkins",
+        username="jenkins_user",
+        password="jenkins_password",
     )
 
 
 def test_version(client):
     import pkg_resources
 
-    version = pkg_resources.get_distribution('jobs_done10').version
-    response = client.post(json={'test': True})
-    assert response.data.decode('UTF-8') == f'jobs_done10 ver. {version}'
+    version = pkg_resources.get_distribution("jobs_done10").version
+    response = client.post(json={"test": True})
+    assert response.data.decode("UTF-8") == f"jobs_done10 ver. {version}"
 
 
 def test_error_handling(client, post_json_data, mocker):
     import mailer
 
-    mocker.patch.object(mailer.Mailer, 'send', autospec=True)
-    mocker.spy(mailer.Mailer, '__init__')
+    mocker.patch.object(mailer.Mailer, "send", autospec=True)
+    mocker.spy(mailer.Mailer, "__init__")
 
-    del post_json_data['eventKey']
+    del post_json_data["eventKey"]
     response = client.post(json=post_json_data)
     assert response.status_code == 500
-    assert response.mimetype == 'text/html'
-    obtained_message = response.data.decode('UTF-8')
-    assert "ERROR processing request: <Request 'http://localhost/' [POST]>" in obtained_message
+    assert response.mimetype == "text/html"
+    obtained_message = response.data.decode("UTF-8")
+    assert (
+        "ERROR processing request: <Request 'http://localhost/' [POST]>"
+        in obtained_message
+    )
     assert "JSON data:" in obtained_message
     assert "Traceback (most recent call last):" in obtained_message
     assert "Email sent to bugreport+jenkins@esss.co" in obtained_message
@@ -150,17 +222,20 @@ def test_error_handling(client, post_json_data, mocker):
     assert mailer.Mailer.send.call_count == 1
     args, kwargs = mailer.Mailer.__init__.call_args
     assert kwargs == dict(
-        host='smtp.example.com',
+        host="smtp.example.com",
         port=5900,
         use_tls=True,
-        usr='email_user',
-        pwd='email_password',
+        usr="email_user",
+        pwd="email_password",
     )
     args, kwargs = mailer.Mailer.send.call_args
     assert len(args) == 2  # (self, message)
     message = args[-1]
-    assert message.To == ['bugreport+jenkins@esss.co']
-    assert message.From == 'JobsDone Bot <jobsdone@example.com>'
-    assert message.Subject == 'JobsDone failure during push to ESSS/eden (stable-pwda11-master @ 8522b06)'
-    assert message.charset == 'UTF-8'
-    assert 'An error happened when processing your push' in message.Body
+    assert message.To == ["bugreport+jenkins@esss.co"]
+    assert message.From == "JobsDone Bot <jobsdone@example.com>"
+    assert (
+        message.Subject
+        == "JobsDone failure during push to ESSS/eden (stable-pwda11-master @ 8522b06)"
+    )
+    assert message.charset == "UTF-8"
+    assert "An error happened when processing your push" in message.Body
diff --git a/src/jobs_done10/xml_factory/_tests/__init__.py b/src/jobs_done10/xml_factory/_tests/__init__.py
index 139597f..e69de29 100644
--- a/src/jobs_done10/xml_factory/_tests/__init__.py
+++ b/src/jobs_done10/xml_factory/_tests/__init__.py
@@ -1,2 +0,0 @@
-
-
diff --git a/src/jobs_done10/xml_factory/_tests/test_xml_factory.py b/src/jobs_done10/xml_factory/_tests/test_xml_factory.py
index 23e2fd1..530b47e 100644
--- a/src/jobs_done10/xml_factory/_tests/test_xml_factory.py
+++ b/src/jobs_done10/xml_factory/_tests/test_xml_factory.py
@@ -1,86 +1,79 @@
-
-
 from io import StringIO
 from textwrap import dedent
 from xml.etree import ElementTree
 
 import pytest
 
-from jobs_done10.xml_factory import WritePrettyXML, WritePrettyXMLElement, XmlFactory
+from jobs_done10.xml_factory import WritePrettyXML
+from jobs_done10.xml_factory import WritePrettyXMLElement
+from jobs_done10.xml_factory import XmlFactory
 
 
-
-#===================================================================================================
+# ===================================================================================================
 # Test
-#===================================================================================================
+# ===================================================================================================
 class Test(object):
-
     def testSimplest(self):
-        '''\
+        """\
         <?xml version="1.0" ?>
         <user>
           <name>Alpha</name>
           <login>Bravo</login>
-        </user>'''
-        factory = XmlFactory('user')
-        factory['name'] = 'Alpha'
-        factory['login'] = 'Bravo'
+        </user>"""
+        factory = XmlFactory("user")
+        factory["name"] = "Alpha"
+        factory["login"] = "Bravo"
 
-        assert (
-            factory.GetContents(xml_header=True)
-            == dedent(self.testSimplest.__doc__)
-        )
+        assert factory.GetContents(xml_header=True) == dedent(self.testSimplest.__doc__)
         assert factory.AsDict() == {"login": "Bravo", "name": "Alpha"}
         assert factory.AsJson() == '{"name": "Alpha", "login": "Bravo"}'
 
-
     def testSimple(self):
-        '''\
+        """\
         <user>
           <name>Alpha</name>
           <login>Bravo</login>
           <location>
             <city>Charlie</city>
           </location>
-        </user>'''
-        factory = XmlFactory('user')
-        factory['name'] = 'Alpha'
-        factory['login'] = 'Bravo'
-        factory['location/city'] = 'Charlie'
-
+        </user>"""
+        factory = XmlFactory("user")
+        factory["name"] = "Alpha"
+        factory["login"] = "Bravo"
+        factory["location/city"] = "Charlie"
+
+        assert factory.GetContents() == dedent(self.testSimple.__doc__)
+        assert factory.AsDict() == {
+            "login": "Bravo",
+            "name": "Alpha",
+            "location": {"city": "Charlie"},
+        }
         assert (
-            factory.GetContents()
-            == dedent(self.testSimple.__doc__)
+            factory.AsJson()
+            == '{"name": "Alpha", "login": "Bravo", "location": {"city": "Charlie"}}'
         )
-        assert factory.AsDict() == {"login": "Bravo", "name": "Alpha", "location": {"city": "Charlie"}}
-        assert factory.AsJson() == '{"name": "Alpha", "login": "Bravo", "location": {"city": "Charlie"}}'
-
 
     def testAttributes(self):
-        '''\
+        """\
         <root>
           <alpha one="1" two="2">Alpha</alpha>
           <bravo>
             <charlie three="3"/>
           </bravo>
-        </root>'''
-        factory = XmlFactory('root')
-        factory['alpha'] = 'Alpha'
-        factory['alpha@one'] = '1'
-        factory['alpha@two'] = '2'
-        factory['bravo/charlie@three'] = '3'
-
-        assert (
-            factory.GetContents()
-            == dedent(self.testAttributes.__doc__)
-        )
+        </root>"""
+        factory = XmlFactory("root")
+        factory["alpha"] = "Alpha"
+        factory["alpha@one"] = "1"
+        factory["alpha@two"] = "2"
+        factory["bravo/charlie@three"] = "3"
+
+        assert factory.GetContents() == dedent(self.testAttributes.__doc__)
         # We're ignoring attributes and empty tags for now.
         assert factory.AsDict() == {"alpha": "Alpha", "bravo": {"charlie": None}}
         assert factory.AsJson() == '{"alpha": "Alpha", "bravo": {"charlie": null}}'
 
-
     def testRepeatingTags(self):
-        '''\
+        """\
         <root>
           <elements>
             <name>Alpha</name>
@@ -98,26 +91,30 @@ def testRepeatingTags(self):
               <name>Charlie</name>
             </component>
           </components>
-        </root>'''
-        factory = XmlFactory('root')
-        factory['elements/name'] = 'Alpha'
-        factory['elements/name+'] = 'Bravo'
-        factory['elements/name+'] = 'Charlie'
-
-        factory['components/component+/name'] = 'Alpha'
-        factory['components/component+/name'] = 'Bravo'
-        factory['components/component+/name'] = 'Charlie'
-
+        </root>"""
+        factory = XmlFactory("root")
+        factory["elements/name"] = "Alpha"
+        factory["elements/name+"] = "Bravo"
+        factory["elements/name+"] = "Charlie"
+
+        factory["components/component+/name"] = "Alpha"
+        factory["components/component+/name"] = "Bravo"
+        factory["components/component+/name"] = "Charlie"
+
+        assert factory.GetContents() == dedent(self.testRepeatingTags.__doc__)
+        assert factory.AsDict() == {
+            "elements": {"name": ["Alpha", "Bravo", "Charlie"]},
+            "components": {
+                "component": [{"name": "Alpha"}, {"name": "Bravo"}, {"name": "Charlie"}]
+            },
+        }
         assert (
-            factory.GetContents()
-            == dedent(self.testRepeatingTags.__doc__)
+            factory.AsJson()
+            == '{"elements": {"name": ["Alpha", "Bravo", "Charlie"]}, "components": {"component": [{"name": "Alpha"}, {"name": "Bravo"}, {"name": "Charlie"}]}}'
         )
-        assert factory.AsDict() == {"elements": {"name": ["Alpha", "Bravo", "Charlie"]}, "components": {"component": [{"name": "Alpha"}, {"name": "Bravo"}, {"name": "Charlie"}]}}
-        assert factory.AsJson() == '{"elements": {"name": ["Alpha", "Bravo", "Charlie"]}, "components": {"component": [{"name": "Alpha"}, {"name": "Bravo"}, {"name": "Charlie"}]}}'
-
 
     def testHudsonJob(self):
-        '''\
+        """\
         <project>
           <actions/>
           <description/>
@@ -140,56 +137,47 @@ def testHudsonJob(self):
           <concurrentBuild>false</concurrentBuild>
           <buildWrappers/>
           <customWorkspace>WORKSPACE</customWorkspace>
-        </project>'''
-        factory = XmlFactory('project')
-        factory['actions']
-        factory['description']
-        factory['logRotator/daysToKeep'] = '7'
-        factory['logRotator/numToKeep'] = '7'
-        factory['keepDependencies'] = 'false'
-        factory['properties']
-        factory['scm@class'] = 'hudson.scm.SubversionSCM'
-        factory['scm/useUpdate'] = 'true'
-        factory['scm/excludedRegions']
-        factory['scm/excludedUsers']
-        factory['scm/excludedRevprop']
-        factory['assignedNode'] = 'KATARN'
-        factory['canRoam'] = 'false'
-        factory['disabled'] = 'false'
-        factory['blockBuildWhenUpstreamBuilding'] = 'true'
-        factory['concurrentBuild'] = 'false'
-        factory['buildWrappers']
-        factory['customWorkspace'] = 'WORKSPACE'
-
-        assert (
-            factory.GetContents()
-            == dedent(self.testHudsonJob.__doc__)
-        )
-
+        </project>"""
+        factory = XmlFactory("project")
+        factory["actions"]
+        factory["description"]
+        factory["logRotator/daysToKeep"] = "7"
+        factory["logRotator/numToKeep"] = "7"
+        factory["keepDependencies"] = "false"
+        factory["properties"]
+        factory["scm@class"] = "hudson.scm.SubversionSCM"
+        factory["scm/useUpdate"] = "true"
+        factory["scm/excludedRegions"]
+        factory["scm/excludedUsers"]
+        factory["scm/excludedRevprop"]
+        factory["assignedNode"] = "KATARN"
+        factory["canRoam"] = "false"
+        factory["disabled"] = "false"
+        factory["blockBuildWhenUpstreamBuilding"] = "true"
+        factory["concurrentBuild"] = "false"
+        factory["buildWrappers"]
+        factory["customWorkspace"] = "WORKSPACE"
+
+        assert factory.GetContents() == dedent(self.testHudsonJob.__doc__)
 
     def testTriggerClass(self):
-        '''\
+        """\
         <root>
           <triggers class="vector"/>
-        </root>'''
+        </root>"""
         # Simulating the use for HudsonJobGenerator._CreateTriggers
-        factory = XmlFactory('root')
-        triggers = factory['triggers']
-        triggers['@class'] = 'vector'
-
-        assert (
-            factory.GetContents()
-            == dedent(self.testTriggerClass.__doc__)
-        )
+        factory = XmlFactory("root")
+        triggers = factory["triggers"]
+        triggers["@class"] = "vector"
 
+        assert factory.GetContents() == dedent(self.testTriggerClass.__doc__)
 
     def testTypeError(self):
         with pytest.raises(TypeError):
             XmlFactory(9)
 
-
     def testPrettyXMLToStream(self, input_xml):
-        '''\
+        """\
         <root>
           <alpha enabled="true">
             <bravo>
@@ -198,22 +186,21 @@ def testPrettyXMLToStream(self, input_xml):
             <bravo.one/>
             <delta>XXX</delta>
           </alpha>
-        </root>'''
+        </root>"""
         iss = StringIO(input_xml)
         oss = StringIO()
 
         WritePrettyXML(iss, oss)
         assert oss.getvalue() == dedent(self.testPrettyXMLToStream.__doc__)
 
-
     def testPrettyXMLToFile(self, input_xml, tmpdir):
         iss = StringIO(input_xml)
-        obtained_filename = tmpdir / 'pretty.obtained.xml'
+        obtained_filename = tmpdir / "pretty.obtained.xml"
 
-        with open(str(obtained_filename), 'w') as f:
+        with open(str(obtained_filename), "w") as f:
             WritePrettyXML(iss, f)
         assert obtained_filename.read() == dedent(
-            '''\
+            """\
             <root>
               <alpha enabled="true">
                 <bravo>
@@ -222,24 +209,25 @@ def testPrettyXMLToFile(self, input_xml, tmpdir):
                 <bravo.one/>
                 <delta>XXX</delta>
               </alpha>
-            </root>'''
+            </root>"""
         )
 
-
     def testEscape(self):
-        element = ElementTree.Element('root')
-        element.attrib['name'] = '<no>'
-        element.text = '> 3'
+        element = ElementTree.Element("root")
+        element.attrib["name"] = "<no>"
+        element.text = "> 3"
         oss = StringIO()
         WritePrettyXMLElement(oss, element)
         assert oss.getvalue() == '<root name="&lt;no&gt;">&gt; 3</root>'
 
         element = ElementTree.fromstring(oss.getvalue())
-        assert element.attrib['name'] == '<no>'
-        assert element.text == '> 3'
+        assert element.attrib["name"] == "<no>"
+        assert element.text == "> 3"
 
 
 @pytest.fixture
 def input_xml():
-    return ('<root><alpha enabled="true"><bravo><charlie/></bravo><bravo.one/>'
-            '<delta>XXX</delta></alpha></root>')
\ No newline at end of file
+    return (
+        '<root><alpha enabled="true"><bravo><charlie/></bravo><bravo.one/>'
+        "<delta>XXX</delta></alpha></root>"
+    )