Repository: ModioAB/caramel (language: python)

Issue:
Drop python2 compatibility and make the CI happier
Dropping Python2 allows us to kill off flake8's warning about import order.

On top of that, since Python now has a useful code formatter in black, run that on the entire code-base to make sure it follows a consistent style.

This branch is based on top of the gitlab-ci branch, and should hopefully make the branch turn green completely in CI.


Discussion / hints:

Fix patch:
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..171d14e
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,76 @@
+---
+# Keep the includes first to illustrate that definitions that everything that
+# follows override included definitions.
+include:
+  # https://docs.gitlab.com/ee/ci/yaml/README.html#includefile
+  - project: ModioAB/CI
+    ref: main
+    file:
+      - /ci/default.yml
+      - /ci/rebase.yml
+
+workflow:
+  # Similar to "Branch pipelines" definition,
+  # but with an exception of "push" to avoid duplicates
+  rules:
+    - if: '$CI_PIPELINE_SOURCE == "push"'
+      when: never
+    - if: $CI_COMMIT_TAG
+    - if: $CI_COMMIT_BRANCH
+
+stages:
+  - test
+  - rebase
+
+caramel:test:
+  stage: test
+  when: always
+  image: ${PYTHON_IMAGE}
+  before_script:
+    - pip3 install .
+  script:
+    - python3 -m unittest discover
+
+rebase:test:
+  extends: .rebase
+  stage: rebase
+  needs:
+    - caramel:test
+  script:
+    - pip3 install .
+    - git every
+      -x 'pip3 install --editable .'
+      -x 'flake8 caramel/ tests/'
+      -x 'black --check caramel/ tests/'
+
+caramel:black:
+  stage: test
+  when: always
+  image: ${PYTHON_IMAGE}
+  before_script:
+    - pip3 install black
+  script:
+    - black --check --diff caramel/ tests/
+
+caramel:flake:
+  stage: test
+  when: always
+  image: ${PYTHON_IMAGE}
+  before_script:
+    - pip3 install flake8
+  script:
+    - flake8 caramel/ tests/
+
+rebase:check:
+  extends: .rebase
+  stage: rebase
+  needs:
+    - caramel:flake
+    - caramel:black
+  script:
+    - python3 -m pip install black flake8
+    # Always install "." first to track possible dependency changes
+    - git every
+      -x 'pip3 install --editable .'
+      -x 'flake8 caramel/ tests/'
+      -x 'black --check caramel/ tests/'
diff --git a/caramel/__init__.py b/caramel/__init__.py
index d44f281..5db3557 100644
--- a/caramel/__init__.py
+++ b/caramel/__init__.py
@@ -1,27 +1,15 @@
 #! /usr/bin/env python
 # vim: expandtab shiftwidth=4 softtabstop=4 tabstop=17 filetype=python :
-
-# Make things as three-ish as possible (requires python >= 2.6)
-from __future__ import (unicode_literals, print_function,
-                        absolute_import, division)
-# Namespace cleanup
-del unicode_literals, print_function, absolute_import, division
-
-#
-# ----- End header -----
-#
-
 from pyramid.config import Configurator
 from sqlalchemy import engine_from_config
 
 from .models import (
     init_session,
-    )
+)
 
 
 def main(global_config, **settings):
-    """ This function returns a Pyramid WSGI application.
-    """
+    """This function returns a Pyramid WSGI application."""
     engine = engine_from_config(settings, "sqlalchemy.")
     init_session(engine)
     config = Configurator(settings=settings)
diff --git a/caramel/models.py b/caramel/models.py
index 180c608..83d7071 100644
--- a/caramel/models.py
+++ b/caramel/models.py
@@ -179,9 +179,7 @@ def subject(self):
     @_reify
     def subject_components(self):
         components = self.subject.get_components()
-        return tuple(
-            (n.decode("utf8"), v.decode("utf8")) for n, v in components
-        )
+        return tuple((n.decode("utf8"), v.decode("utf8")) for n, v in components)
 
     @classmethod
     def valid(cls):
@@ -212,10 +210,7 @@ def refreshable(cls):
         # instead batch load the certificates at once
         all_signed = _sa.select([Certificate.csr_id])
         return (
-            cls.query()
-            .filter_by(rejected=False)
-            .filter(CSR.id.in_(all_signed))
-            .all()
+            cls.query().filter_by(rejected=False).filter(CSR.id.in_(all_signed)).all()
         )
 
     @classmethod
@@ -265,8 +260,7 @@ def __init__(self, csr, addr):
 
     def __str__(self):
         return (
-            "<{0.__class__.__name__} id={0.id} "
-            "csr={0.csr.sha256sum} when={0.when}>"
+            "<{0.__class__.__name__} id={0.id} " "csr={0.csr.sha256sum} when={0.when}>"
         ).format(self)
 
     def __repr__(self):
@@ -344,9 +338,7 @@ def __repr__(self):
         return "<{0.__class__.__name__} id={0.id}>".format(self)
 
     @classmethod
-    def sign(
-        cls, CSR, ca, lifetime=_datetime.timedelta(30 * 3), backdate=False
-    ):
+    def sign(cls, CSR, ca, lifetime=_datetime.timedelta(30 * 3), backdate=False):
         """Takes a CSR, signs it, generating and returning a Certificate.
         backdate causes the CA to set "notBefore" of signed certificates to
         match that of the CA Certificate. This is an ugly workaround for a
diff --git a/caramel/scripts/autosign.py b/caramel/scripts/autosign.py
index 3e83f4f..fe85883 100644
--- a/caramel/scripts/autosign.py
+++ b/caramel/scripts/autosign.py
@@ -66,8 +66,7 @@ def mainloop(delay, ca, delta):
     with concurrent.futures.ThreadPoolExecutor(max_workers=16) as executor:
         while True:
             csrs = models.CSR.unsigned()
-            futures = [executor.submit(csr_sign, csr, ca, delta)
-                       for csr in csrs]
+            futures = [executor.submit(csr_sign, csr, ca, delta) for csr in csrs]
 
             time.sleep(delay)
             for future in concurrent.futures.as_completed(futures):
@@ -82,8 +81,7 @@ def cmdline():
     parser = argparse.ArgumentParser()
     parser.add_argument("inifile")
     parser.add_argument("--delay", help="How long to sleep. (ms)")
-    parser.add_argument("--valid",
-                        help="How many hours the certificate is valid for")
+    parser.add_argument("--valid", help="How many hours the certificate is valid for")
     args = parser.parse_args()
     return args
 
@@ -101,11 +99,11 @@ def main():
     logger.setLevel(logging.DEBUG)
     args = cmdline()
     env = bootstrap(args.inifile)
-    settings, closer = env['registry'].settings, env['closer']
-    engine = create_engine(settings['sqlalchemy.url'])
+    settings, closer = env["registry"].settings, env["closer"]
+    engine = create_engine(settings["sqlalchemy.url"])
     models.init_session(engine)
-    delay = int(settings.get('delay', 500)) / 1000
-    valid = int(settings.get('valid', 3))
+    delay = int(settings.get("delay", 500)) / 1000
+    valid = int(settings.get("valid", 3))
     delta = datetime.timedelta(days=0, hours=valid)
     del valid
 
diff --git a/caramel/scripts/generate_ca.py b/caramel/scripts/generate_ca.py
index 5f506b5..17ac2f5 100644
--- a/caramel/scripts/generate_ca.py
+++ b/caramel/scripts/generate_ca.py
@@ -16,10 +16,11 @@
 
 CA_EXTENSIONS = [
     # Key usage for a CA cert.
-    _crypto.X509Extension(b"basicConstraints", critical=True,
-                          value=b"CA:true, pathlen:0"),
+    _crypto.X509Extension(
+        b"basicConstraints", critical=True, value=b"CA:true, pathlen:0"
+    ),
     # no cRLSign as we do not use CRLs in caramel.
-    _crypto.X509Extension(b"keyUsage", critical=True, value=b"keyCertSign")
+    _crypto.X509Extension(b"keyUsage", critical=True, value=b"keyCertSign"),
 ]
 
 
@@ -28,7 +29,7 @@ def _crypto_patch():
     https://github.com/pyca/pyopenssl/pull/115 has a pull&fix for it
     https://github.com/pyca/pyopenssl/issues/129 is an open issue
     about it."""
-    _crypto._lib.ASN1_STRING_set_default_mask_asc(b'utf8only')
+    _crypto._lib.ASN1_STRING_set_default_mask_asc(b"utf8only")
 
 
 _crypto_patch()
@@ -37,8 +38,8 @@ def _crypto_patch():
 # Hack hack. :-)
 def CA_LIFE():
     d = datetime.date.today()
-    t = datetime.date(d.year+CA_YEARS, d.month, d.day)
-    return int((t-d).total_seconds())
+    t = datetime.date(d.year + CA_YEARS, d.month, d.day)
+    return int((t - d).total_seconds())
 
 
 # adapted from models.py
@@ -48,7 +49,7 @@ def components(subject):
 
 
 def matching_template(x509, cacert):
-    """ Takes a subject as a dict, and returns if all required fields
+    """Takes a subject as a dict, and returns if all required fields
     match. Otherwise raises exception"""
 
     def later_check(subject):
@@ -71,8 +72,7 @@ def later_check(subject):
 
     for (ca, sub) in zip(casubject, subject):
         if ca != sub:
-            raise ValueError("Subject needs to match CA cert:"
-                             "{}".format(casubject))
+            raise ValueError("Subject needs to match CA cert:" "{}".format(casubject))
 
 
 def sign_req(req, cacert, cakey):
@@ -104,18 +104,19 @@ def sign_req(req, cacert, cakey):
 
     cacert = cert
 
-    extension = _crypto.X509Extension(b"subjectKeyIdentifier",
-                                      critical=False,
-                                      value=b"hash",
-                                      subject=cert)
+    extension = _crypto.X509Extension(
+        b"subjectKeyIdentifier", critical=False, value=b"hash", subject=cert
+    )
     cert.add_extensions([extension])
 
     # We need subjectKeyIdentifier to be added before we can add
     # authorityKeyIdentifier.
-    extension = _crypto.X509Extension(b"authorityKeyIdentifier",
-                                      critical=False,
-                                      value=b"issuer:always,keyid:always",
-                                      issuer=cacert)
+    extension = _crypto.X509Extension(
+        b"authorityKeyIdentifier",
+        critical=False,
+        value=b"issuer:always,keyid:always",
+        issuer=cacert,
+    )
     cert.add_extensions([extension])
 
     cert.sign(cakey, "sha512")
@@ -147,10 +148,9 @@ def create_ca(subject):
 
 
 def write_files(key, keyname, cert, certname):
-
     def writefile(data, name):
-        with open(name, 'w') as f:
-            stream = data.decode('utf8')
+        with open(name, "w") as f:
+            stream = data.decode("utf8")
             f.write(stream)
 
     _key = _crypto.dump_privatekey(_crypto.FILETYPE_PEM, key)
@@ -188,8 +188,7 @@ def build_ca(keyname, certname):
     template = tuple(template)
 
     key, req, cert = create_ca(template)
-    write_files(key=key, keyname=keyname,
-                cert=cert, certname=certname)
+    write_files(key=key, keyname=keyname, cert=cert, certname=certname)
 
 
 def main():
diff --git a/caramel/scripts/initializedb.py b/caramel/scripts/initializedb.py
index 8e599b9..763912d 100644
--- a/caramel/scripts/initializedb.py
+++ b/caramel/scripts/initializedb.py
@@ -1,16 +1,5 @@
 #! /usr/bin/env python
 # vim: expandtab shiftwidth=4 softtabstop=4 tabstop=17 filetype=python :
-
-# Make things as three-ish as possible (requires python >= 2.6)
-from __future__ import (unicode_literals, print_function,
-                        absolute_import, division)
-# Namespace cleanup
-del unicode_literals, print_function, absolute_import, division
-
-#
-# ----- End header -----
-#
-
 import os
 import sys
 
@@ -19,15 +8,14 @@
 from pyramid.paster import (
     get_appsettings,
     setup_logging,
-    )
+)
 
 from ..models import init_session
 
 
 def usage(argv):
     cmd = os.path.basename(argv[0])
-    print('usage: %s <config_uri>\n'
-          '(example: "%s development.ini")' % (cmd, cmd))
+    print("usage: %s <config_uri>\n" '(example: "%s development.ini")' % (cmd, cmd))
     sys.exit(1)
 
 
@@ -37,5 +25,5 @@ def main(argv=sys.argv):
     config_uri = argv[1]
     setup_logging(config_uri)
     settings = get_appsettings(config_uri)
-    engine = engine_from_config(settings, 'sqlalchemy.')
+    engine = engine_from_config(settings, "sqlalchemy.")
     init_session(engine, create=True)
diff --git a/caramel/scripts/tool.py b/caramel/scripts/tool.py
index 46d9837..722897c 100644
--- a/caramel/scripts/tool.py
+++ b/caramel/scripts/tool.py
@@ -88,9 +88,7 @@ def unsigned_last(csr):
 
     for csr_id, csr_commonname, csr_sha256sum, not_after in valid_requests:
         not_after = "----------" if not_after is None else str(not_after)
-        output = " ".join(
-            (str(csr_id), csr_commonname, csr_sha256sum, not_after)
-        )
+        output = " ".join((str(csr_id), csr_commonname, csr_sha256sum, not_after))
         # TODO: Add lifetime of latest (fetched?) cert for the key.
         print(output)
 
@@ -183,9 +181,7 @@ def csr_resign(ca, lifetime_short, lifetime_long, backdate):
         except Exception:
             error_out("Not found or some other error")
         futures = (
-            executor.submit(
-                refresh, csr, ca, lifetime_short, lifetime_long, backdate
-            )
+            executor.submit(refresh, csr, ca, lifetime_short, lifetime_long, backdate)
             for csr in csrlist
         )
         for future in concurrent.futures.as_completed(futures):
diff --git a/caramel/views.py b/caramel/views.py
index 2500d63..ff42e99 100644
--- a/caramel/views.py
+++ b/caramel/views.py
@@ -1,44 +1,34 @@
 #! /usr/bin/env python
 # vim: expandtab shiftwidth=4 softtabstop=4 tabstop=17 filetype=python :
-
-# Make things as three-ish as possible (requires python >= 2.6)
-from __future__ import (unicode_literals, print_function,
-                        absolute_import, division)
-# Namespace cleanup
-del unicode_literals, print_function, absolute_import, division
-
-#
-# ----- End header -----
-#
+from hashlib import sha256
+from datetime import datetime
 
 from pyramid.response import Response
+from pyramid.view import view_config
 from pyramid.httpexceptions import (
     HTTPLengthRequired,
     HTTPRequestEntityTooLarge,
     HTTPBadRequest,
     HTTPNotFound,
     HTTPForbidden,
-    HTTPError
-    )
-from pyramid.view import view_config
+    HTTPError,
+)
 
-from hashlib import sha256
-from datetime import datetime
+from sqlalchemy.exc import IntegrityError
+from sqlalchemy.orm.exc import NoResultFound
 
 from .models import (
     CSR,
     AccessLog,
     SigningCert,
-    )
+)
 
-from sqlalchemy.exc import IntegrityError
-from sqlalchemy.orm.exc import NoResultFound
 
 # Maximum length allowed for csr uploads.
 # 2 kbyte should be enough for up to 4 kbit keys.
 # XXX: This should probably be handled outside of app (i.e. by the
 #      server), or at least be configurable.
-_MAXLEN = 2 * 2**10
+_MAXLEN = 2 * 2 ** 10
 
 
 def raise_for_length(req, limit=_MAXLEN):
@@ -48,9 +38,7 @@ def raise_for_length(req, limit=_MAXLEN):
     if length is None:
         raise HTTPLengthRequired
     if length > limit:
-        raise HTTPRequestEntityTooLarge(
-            "Max size: {0} kB".format(limit / 2**10)
-            )
+        raise HTTPRequestEntityTooLarge("Max size: {0} kB".format(limit / 2 ** 10))
 
 
 def raise_for_subject(components, required_prefix):
@@ -65,11 +53,7 @@ def raise_for_subject(components, required_prefix):
 # XXX: Is this the right way? Catch-class JSON converter of Exceptions
 @view_config(context=HTTPError)
 def HTTPErrorToJson(exc, request):
-    exc.json_body = {
-        "status": exc.code,
-        "title": exc.title,
-        "detail": exc.detail
-    }
+    exc.json_body = {"status": exc.code, "title": exc.title, "detail": exc.detail}
     exc.content_type = "application/problem+json"
     request.response = exc
     return request.response
@@ -123,23 +107,23 @@ def cert_fetch(request):
     if cert:
         if datetime.utcnow() < cert.not_after:
             # XXX: appropriate content-type is ... ?
-            return Response(cert.pem,
-                            content_type="application/octet-stream",
-                            charset="UTF-8")
+            return Response(
+                cert.pem, content_type="application/octet-stream", charset="UTF-8"
+            )
     request.response.status_int = 202
     return csr
 
 
-@view_config(route_name="ca", request_method="GET",
-             renderer="string", http_cache=3600)
+@view_config(route_name="ca", request_method="GET", renderer="string", http_cache=3600)
 def ca_fetch(request):
-    ca_file = request.registry.settings['ca.cert']
+    ca_file = request.registry.settings["ca.cert"]
     ca = SigningCert.from_files(ca_file)
     return ca.pem.decode("utf8")
 
 
-@view_config(route_name="cabundle", request_method="GET",
-             renderer="string", http_cache=3600)
+@view_config(
+    route_name="cabundle", request_method="GET", renderer="string", http_cache=3600
+)
 def ca_bundle_fetch(request):
     """Attempt to return a bunle of all our intermediates"""
     bundle = ca_fetch(request)
diff --git a/setup.cfg b/setup.cfg
index dbdb1ac..8e41b59 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -25,3 +25,7 @@ domain = caramel
 input_file = caramel/locale/caramel.pot
 output_dir = caramel/locale
 previous = true
+
+[flake8]
+max-line-length = 88
+extend-ignore = E203


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
index 372f8b0..3daf2a6 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,31 +1,17 @@
 #! /usr/bin/env python
 # vim: expandtab shiftwidth=4 softtabstop=4 tabstop=17 filetype=python :
-
-# Make things as three-ish as possible (requires python >= 2.6)
-from __future__ import (unicode_literals, print_function,
-                        absolute_import, division)
-# Namespace cleanup
-del unicode_literals, print_function, absolute_import, division
-
-#
-# ----- End header -----
-#
-
 import unittest
+from itertools import zip_longest
+
 import transaction
 
 from caramel.models import (
     init_session,
     DBSession,
-    )
+)
 
 from . import fixtures
 
-try:
-    from itertools import zip_longest
-except ImportError:
-    from itertools import izip_longest as zip_longest
-
 
 class ModelTestCase(unittest.TestCase):
     @classmethod
@@ -34,6 +20,7 @@ def setUpClass(cls):
         # Clear existing session, if any.
         DBSession.remove()
         from sqlalchemy import create_engine
+
         engine = create_engine("sqlite://")
         init_session(engine, create=True)
         with transaction.manager:
diff --git a/tests/fixtures.py b/tests/fixtures.py
index d83e686..ab894b9 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -1,35 +1,17 @@
 #! /usr/bin/env python
 # vim: expandtab shiftwidth=4 softtabstop=4 tabstop=17 filetype=python :
-
-# Make things as three-ish as possible (requires python >= 2.6)
-from __future__ import (unicode_literals, print_function,
-                        absolute_import, division)
-# Namespace cleanup
-del unicode_literals, print_function, absolute_import, division
-
-#
-# ----- End header -----
-#
-
 from textwrap import dedent
 from hashlib import sha256
 from operator import attrgetter
+from datetime import datetime, timedelta
+from itertools import zip_longest
 
-try:
-    from itertools import zip_longest
-except ImportError:
-    from itertools import izip_longest as zip_longest
+from caramel import models, views
 
-from datetime import datetime, timedelta
 day = timedelta(days=1)
-year = 365 * day                # close enough
+year = 365 * day  # close enough
 now = datetime.now()
 
-from caramel import (
-    models,
-    views,
-    )
-
 
 class defaultproperty(object):
     def __init__(self, func):
@@ -45,8 +27,11 @@ class SimilarityComparable(object):
 
     def match(self, other):
         excludes = set(getattr(self, "_match_excluded_attrs_", ()))
-        attrs = [attr for attr in dir(self)
-                 if not attr.startswith("_") and attr not in excludes]
+        attrs = [
+            attr
+            for attr in dir(self)
+            if not attr.startswith("_") and attr not in excludes
+        ]
         if not attrs:
             return True
         getter = attrgetter(*attrs)
@@ -66,12 +51,14 @@ def __init__(self, _base_=None, **kwds):
 
 
 class CSRFixture(AttributeCollection, SimilarityComparable):
-    __relations = ("accessed", "certificates",)
+    __relations = (
+        "accessed",
+        "certificates",
+    )
 
     @property
     def _match_excluded_attrs_(self):
-        return (super(CSRFixture, self)._match_excluded_attrs_ +
-                self.__relations)
+        return super(CSRFixture, self)._match_excluded_attrs_ + self.__relations
 
     @defaultproperty
     def sha256sum(self):
@@ -118,14 +105,18 @@ def __call__(self, csr):
 
 
 # "Correct" subject prefix for test data
-subject_prefix = (('O', 'Example inc.'), ('OU', 'Example Dept'),)
+subject_prefix = (
+    ("O", "Example inc."),
+    ("OU", "Example Dept"),
+)
 
 
 class CertificateData(object):
     initial = CertificateFixture(
         not_before=now - 2 * year,
         not_after=now + 2 * year,
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE-----
             MIIEVDCCAjygAwIBAgIBZTANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtDYXJh
             bWVsIFNpZ25pbmcgQ2VydGlmaWNhdGUwHhcNMTQwODA4MTM0NDA0WhcNMTQxMDA4
@@ -152,13 +143,15 @@ class CertificateData(object):
             n4rH2rFn8rcQwSaRyE9NcOpirCur43MR42+LAfZq5s9j7CuQJTw6G0wvCHGqRIQ9
             X1qbQOxg6ig=
             -----END CERTIFICATE-----
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
 
     expired = CertificateFixture(
         not_before=initial.not_before,
         not_after=now - 1 * day,
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE-----
             MIIEVTCCAj2gAwIBAgIBaDANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtDYXJh
             bWVsIFNpZ25pbmcgQ2VydGlmaWNhdGUwHhcNMTQwODA4MTM1OTUxWhcNMTQxMDA4
@@ -185,21 +178,29 @@ class CertificateData(object):
             7CRL6mRM5+q4ICfesXzUXpPYjgD2UexIjSJNoYjiu3aKpjwvrZ88t8hPR46Uac7E
             5yvm61Agw/Tg
             -----END CERTIFICATE-----
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
 
     ca_cert = CertificateFixture(
         not_before=initial.not_before,
         not_after=initial.not_after,
-        subject=(('C', 'SE'), ('ST', 'Östergötland'),
-                 ('L', 'Norrköping'), ('O', 'Muppar AB'),
-                 ('OU', 'Muppar Teknik'),
-                 ('CN', 'Caramel Signing Certificate')),
-
-        common_subject=(('C', 'SE'), ('ST', 'Östergötland'),
-                        ('L', 'Norrköping'), ('O', 'Muppar AB')),
-
-        pem=dedent("""\
+        subject=(
+            ("C", "SE"),
+            ("ST", "Östergötland"),
+            ("L", "Norrköping"),
+            ("O", "Muppar AB"),
+            ("OU", "Muppar Teknik"),
+            ("CN", "Caramel Signing Certificate"),
+        ),
+        common_subject=(
+            ("C", "SE"),
+            ("ST", "Östergötland"),
+            ("L", "Norrköping"),
+            ("O", "Muppar AB"),
+        ),
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE-----
             MIIGwDCCBKigAwIBAgIRAJSEOECNQRHkq2SMiaXBGsIwDQYJKoZIhvcNAQENBQAw
             gY4xCzAJBgNVBAYTAlNFMRcwFQYDVQQIDA7DlnN0ZXJnw7Z0bGFuZDEUMBIGA1UE
@@ -239,28 +240,30 @@ class CertificateData(object):
             EybN1zenahQmhX4ljI5OSMYBP3gpkRu2HvP5SOwN28QHq2+mgCFxzdfdtiWoJ8a0
             7eLmmg==
             -----END CERTIFICATE-----
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
     # FIXME: add more certificates here, once we have something to test.
 
 
 class AccessLogData(object):
     initial_1 = AccessLogFixture(
         when=CertificateData.initial.not_before + 30 * day,
-        addr="127.0.0.1",       # XXX: bytestring or unicode string?
-        )
+        addr="127.0.0.1",  # XXX: bytestring or unicode string?
+    )
 
     initial_2 = AccessLogFixture(
         when=now - 3 * day,
         addr="127.0.0.127",
-        )
+    )
 
 
 class CSRData(object):
     initial = CSRFixture(
         orgunit="Example Dept",
         commonname="foo.example.com",
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE REQUEST-----
             MIICjTCCAXUCAQIwSDEVMBMGA1UECgwMRXhhbXBsZSBpbmMuMRUwEwYDVQQLDAxF
             eGFtcGxlIERlcHQxGDAWBgNVBAMMD2Zvby5leGFtcGxlLmNvbTCCASIwDQYJKoZI
@@ -277,11 +280,14 @@ class CSRData(object):
             O+jruZshnFL0KQybIokYGLLcb6NixdsCTSw+rfztuLUEEMP1ozNCgk8TX8mXWduM
             XIP49FHFe6IjLuj0ofRXiJPmS+4ToqRbNIBRoz7kSLov
             -----END CERTIFICATE REQUEST-----
-            """).encode("utf8"),  # py3 dedent can't handle bytes
+            """
+        ).encode(
+            "utf8"
+        ),  # py3 dedent can't handle bytes
         subject_components=(
-            ('O', 'Example inc.'),
-            ('OU', 'Example Dept'),
-            ('CN', 'foo.example.com'),
+            ("O", "Example inc."),
+            ("OU", "Example Dept"),
+            ("CN", "foo.example.com"),
         ),
         accessed=[
             AccessLogData.initial_2,
@@ -290,12 +296,13 @@ class CSRData(object):
         certificates=[
             CertificateData.initial,
         ],
-        )
+    )
 
     with_expired_cert = CSRFixture(
         orgunit="Example Dept",
         commonname="spam.example.com",
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE REQUEST-----
             MIICjjCCAXYCAQIwSTEVMBMGA1UECgwMRXhhbXBsZSBpbmMuMRUwEwYDVQQLDAxF
             eGFtcGxlIERlcHQxGTAXBgNVBAMMEHNwYW0uZXhhbXBsZS5jb20wggEiMA0GCSqG
@@ -312,21 +319,25 @@ class CSRData(object):
             oQN1HuxKCScUJ9Vtr0dnBLGAf62vAqv5yZYhl9Qnt5EJ9OtspWm0e8FwTjNmoA/z
             qnvUxskzM2ItxVV9oa9YDTid0GbJvF67QJQyVIO0Vz4uwg==
             -----END CERTIFICATE REQUEST-----
-            """).encode("utf8"),  # py3 dedent can't handle bytes
+            """
+        ).encode(
+            "utf8"
+        ),  # py3 dedent can't handle bytes
         subject_components=(
-            ('O', 'Example inc.'),
-            ('OU', 'Example Dept'),
-            ('CN', 'spam.example.com'),
+            ("O", "Example inc."),
+            ("OU", "Example Dept"),
+            ("CN", "spam.example.com"),
         ),
         certificates=[
             CertificateData.expired,
         ],
-        )
+    )
 
     good = CSRFixture(
         orgunit="Example Dept",
         commonname="bar.example.com",
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE REQUEST-----
             MIICjTCCAXUCAQIwSDEVMBMGA1UECgwMRXhhbXBsZSBpbmMuMRUwEwYDVQQLDAxF
             eGFtcGxlIERlcHQxGDAWBgNVBAMMD2Jhci5leGFtcGxlLmNvbTCCASIwDQYJKoZI
@@ -343,16 +354,18 @@ class CSRData(object):
             MN1FxWzEy4yXgzW8uv+lX6yyTtkfrC7e3LFiAuoUlBeD5GmsVd30Xz5iGnuQv3d0
             fekjT5Np8XIS2ERJmx4CIjs5VpE1FMNOMoJ35kQpkQaQ
             -----END CERTIFICATE REQUEST-----
-            """).encode("utf8"),
+            """
+        ).encode("utf8"),
         subject_components=(
-            ('O', 'Example inc.'),
-            ('OU', 'Example Dept'),
-            ('CN', 'bar.example.com'),
+            ("O", "Example inc."),
+            ("OU", "Example Dept"),
+            ("CN", "bar.example.com"),
         ),
-        )
+    )
 
     bad_signature = CSRFixture(  # `good` with the subject of `initial`
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE REQUEST-----
             MIIBAjCBrQIBADBIMRUwEwYDVQQKDAxFeGFtcGxlIGluYy4xFTATBgNVBAsMDEV4
             YW1wbGUgRGVwdDEYMBYGA1UEAwwPZm9vLmV4YW1wbGUuY29tMFwwDQYJKoZIhvcN
@@ -361,21 +374,25 @@ class CSRData(object):
             BQUAA0EAcsrzTdYBqlbq/JQaMSEoi64NmoxiC8GGzOaKlTxqRc7PKb+T1wN94PxJ
             faXw8kA8p0E6hmwFAE9QVkuTKvP/eg==
             -----END CERTIFICATE REQUEST-----
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
 
     truncated = CSRFixture(
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE REQUEST-----
             MIIBAjCBrQIBADBIMRUwEwYDVQQKDAxFeGFtcGxlIGluYy4xFTATBgNVBAsMDEV4
             YW1wbGUgRGVwdDEYMBYGA1UEAwwPYmFyLmV4YW1wbGUuY29tMFwwDQYJKoZIhvcN
             AQEBBQADSwAwSAJBAKk2sD6xi/gfO3TVnoGMhUmkPDD17/qYzEvDdw/kponLTdNF
             asGx1//giKSBqBpUFt+KTz3NofK9Pf2qWWDxyUECAwEAAaAAMA0GCSqGSIb3DQEB
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
 
     trailing_content = CSRFixture(
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE REQUEST-----
             MIIBAjCBrQIBADBIMRUwEwYDVQQKDAxFeGFtcGxlIGluYy4xFTATBgNVBAsMDEV4
             YW1wbGUgRGVwdDEYMBYGA1UEAwwPYmFyLmV4YW1wbGUuY29tMFwwDQYJKoZIhvcN
@@ -388,11 +405,13 @@ class CSRData(object):
             bar
             baz
             quux
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
 
     leading_content = CSRFixture(
-        pem=dedent("""\
+        pem=dedent(
+            """\
             foo
             bar
             baz
@@ -405,11 +424,13 @@ class CSRData(object):
             BQUAA0EAcsrzTdYBqlbq/JQaMSEoi64NmoxiC8GGzOaKlTxqRc7PKb+T1wN94PxJ
             faXw8kA8p0E6hmwFAE9QVkuTKvP/eg==
             -----END CERTIFICATE REQUEST-----
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
 
     multi_request = CSRFixture(
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE REQUEST-----
             MIIBAjCBrQIBADBIMRUwEwYDVQQKDAxFeGFtcGxlIGluYy4xFTATBgNVBAsMDEV4
             YW1wbGUgRGVwdDEYMBYGA1UEAwwPZm9vLmV4YW1wbGUuY29tMFwwDQYJKoZIhvcN
@@ -426,11 +447,13 @@ class CSRData(object):
             BQUAA0EAcsrzTdYBqlbq/JQaMSEoi64NmoxiC8GGzOaKlTxqRc7PKb+T1wN94PxJ
             faXw8kA8p0E6hmwFAE9QVkuTKvP/eg==
             -----END CERTIFICATE REQUEST-----
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
 
     not_pem = CSRFixture(
-        pem=dedent("""\
+        pem=dedent(
+            """\
             Egg and Bacon
             Egg, Sausage and Bacon
             Egg and Spam
@@ -441,8 +464,9 @@ class CSRData(object):
             Spam, Spam, Spam, Egg and Spam
             Spam, Spam, Spam, Spam, Spam, Spam, Baked Beans,
                 Spam, Spam, Spam and Spam
-            """).encode("utf8"),
-        )
+            """
+        ).encode("utf8"),
+    )
 
     empty = CSRFixture(pem=b"")
 
@@ -451,7 +475,8 @@ class CSRData(object):
     bad_subject = CSRFixture(
         orgunit="Example test dept.",
         commonname="baz.example.com",
-        pem=dedent("""\
+        pem=dedent(
+            """\
             -----BEGIN CERTIFICATE REQUEST-----
             MIIBCDCBswIBADBOMRUwEwYDVQQKDAxFeGFtcGxlIGluYy4xGzAZBgNVBAsMEkV4
             YW1wbGUgdGVzdCBkZXB0LjEYMBYGA1UEAwwPYmF6LmV4YW1wbGUuY29tMFwwDQYJ
@@ -460,14 +485,15 @@ class CSRData(object):
             SIb3DQEBBQUAA0EAhriivXQYFbdc8QrnDjwVCX8ZGXiGKQEC66LceWDdvOy+mDu8
             gi+L6IgnptU8VmEowAPp0veIH1MWJrnGdp7M0g==
             -----END CERTIFICATE REQUEST-----
-        """).encode("utf-8"),
+        """
+        ).encode("utf-8"),
         subject_components=(
             ("O", "Example inc."),
             ("OU", "Example test dept."),
             ("CN", "baz.example.com"),
         ),
-        )
+    )
 
     large_body = CSRFixture(
         pem="".join(("foobar", "x" * views._MAXLEN)).encode("utf-8"),
-        )
+    )
diff --git a/tests/test_views.py b/tests/test_views.py
index 8f2acf3..46628be 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -1,20 +1,8 @@
 #! /usr/bin/env python
 # vim: expandtab shiftwidth=4 softtabstop=4 tabstop=17 filetype=python :
-
-# Make things as three-ish as possible (requires python >= 2.6)
-from __future__ import (unicode_literals, print_function,
-                        absolute_import, division)
-# Namespace cleanup
-del unicode_literals, print_function, absolute_import, division
-
-#
-# ----- End header -----
-#
-
+import datetime
 import unittest
 import unittest.mock
-import transaction
-import datetime
 
 from pyramid import testing
 from pyramid.response import Response
@@ -23,25 +11,27 @@
     HTTPRequestEntityTooLarge,
     HTTPBadRequest,
     HTTPNotFound,
-    )
+)
 
-from . import fixtures, ModelTestCase
+import transaction
 
 from caramel.models import (
     init_session,
     DBSession,
     CSR,
     AccessLog,
-    )
+)
 from caramel import views
 
+from . import fixtures, ModelTestCase
+
 
 def dummypost(fix, **args):
     req = testing.DummyRequest(**args)
     req.body = fix.pem
     req.content_length = len(req.body)
     req.matchdict["sha256"] = fix.sha256sum
-    req.registry.settings['ca.cert'] = 'abc123.crt'
+    req.registry.settings["ca.cert"] = "abc123.crt"
     return req
 
 
@@ -159,8 +149,9 @@ def test_exists_valid(self):
         self.assertEqual(self.req.response.status_int, 200)
         # Verify there's a new AccessLog entry
         self.assertEqual(csr.accessed[0].addr, self.req.remote_addr)
-        self.assertAlmostEqual(csr.accessed[0].when, now,
-                               delta=datetime.timedelta(seconds=1))
+        self.assertAlmostEqual(
+            csr.accessed[0].when, now, delta=datetime.timedelta(seconds=1)
+        )
 
     def test_exists_expired(self):
         csr = fixtures.CSRData.with_expired_cert()
@@ -173,8 +164,9 @@ def test_exists_expired(self):
         self.assertEqual(self.req.response.status_int, 202)
         # Verify there's a new AccessLog entry
         self.assertEqual(csr.accessed[0].addr, self.req.remote_addr)
-        self.assertAlmostEqual(csr.accessed[0].when, now,
-                               delta=datetime.timedelta(seconds=1))
+        self.assertAlmostEqual(
+            csr.accessed[0].when, now, delta=datetime.timedelta(seconds=1)
+        )
 
     def test_not_signed(self):
         csr = fixtures.CSRData.good()
@@ -187,8 +179,9 @@ def test_not_signed(self):
         self.assertEqual(self.req.response.status_int, 202)
         # Verify there's a new AccessLog entry
         self.assertEqual(csr.accessed[0].addr, self.req.remote_addr)
-        self.assertAlmostEqual(csr.accessed[0].when, now,
-                               delta=datetime.timedelta(seconds=1))
+        self.assertAlmostEqual(
+            csr.accessed[0].when, now, delta=datetime.timedelta(seconds=1)
+        )
         pass
 
 
@@ -197,9 +190,11 @@ class TestMyView(unittest.TestCase):
     def setUp(self):
         self.config = testing.setUp()
         from sqlalchemy import create_engine
+
         engine = create_engine("sqlite://")
         init_session(engine, create=True)
         from caramel.models import MyModel
+
         with transaction.manager:
             model = MyModel(name="one", value=55)
             model.save()
@@ -210,6 +205,7 @@ def tearDown(self):
 
     def test_it(self):
         from caramel.views import my_view
+
         request = testing.DummyRequest()
         info = my_view(request)
         self.assertEqual(info["one"].name, "one")