Repository: projectfluent/python-fluent (language: python)

Issue:
Drop Python 2.7, 3.5 support, add 3.6-3.9 for fluent.syntax
This is pretty mechanic, doing Syntax first, as fluent.runtime and fluent.pygments depend on released versions of fluent.syntax.

* Update metadata and testing matrix
* Drop six
* pyupgrade --py36-plus
* Version bump and release notes

pyupgrade comes from https://github.com/asottile/pyupgrade.

I did try `--py39-plus` just for the kicks of it, and that didn't show anything more.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/fluent.integration.yml b/.github/workflows/fluent.integration.yml
index 45290efc..d4c3f456 100644
--- a/.github/workflows/fluent.integration.yml
+++ b/.github/workflows/fluent.integration.yml
@@ -37,7 +37,7 @@ jobs:
         run: |
           python -m pip install wheel
           python -m pip install --upgrade pip
-          python -m pip install . mock
+          python -m pip install .
       - name: Install latest fluent.syntax
         working-directory: ./fluent.syntax
         run: |
diff --git a/.github/workflows/fluent.runtime.yml b/.github/workflows/fluent.runtime.yml
index e98fa5d6..ce8ef5a0 100644
--- a/.github/workflows/fluent.runtime.yml
+++ b/.github/workflows/fluent.runtime.yml
@@ -24,8 +24,11 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [2.7, 3.5, 3.6, 3.7, 3.8, pypy2, pypy3]
-        fluent-syntax: [0.17.0]
+        python-version: [3.6, 3.7, 3.8, 3.9, pypy3]
+        fluent-syntax: [0.18.1]
+        include:
+          - python-version: 3.9
+            fluent-syntax: 0.17.0
     steps:
       - uses: actions/checkout@v2
       - uses: actions/setup-python@v2
@@ -37,7 +40,7 @@ jobs:
           python -m pip install wheel
           python -m pip install --upgrade pip
           python -m pip install fluent.syntax==${{ matrix.fluent-syntax }}
-          python -m pip install . mock
+          python -m pip install .
       - name: Test
         working-directory: ./fluent.runtime
         run: |
@@ -49,7 +52,7 @@ jobs:
       - uses: actions/checkout@v2
       - uses: actions/setup-python@v2
         with:
-          python-version: 3.7
+          python-version: 3.9
       - name: Install dependencies
         run: |
           python -m pip install wheel
diff --git a/fluent.pygments/CHANGELOG.rst b/fluent.pygments/CHANGELOG.rst
index 49403011..49e7695a 100644
--- a/fluent.pygments/CHANGELOG.rst
+++ b/fluent.pygments/CHANGELOG.rst
@@ -1,6 +1,12 @@
 Changelog
 =========
 
+fluent.runtime 2.0 (TBD)
+-----------------------------------
+
+* Drop support for Python 2.7 and 3.5
+* Add support for Python 3.6 through 3.9
+
 fluent.pygments 1.0 (May 20, 2020)
 ----------------------------------
 
diff --git a/fluent.pygments/fluent/pygments/cli.py b/fluent.pygments/fluent/pygments/cli.py
index f58513be..1a93b46e 100644
--- a/fluent.pygments/fluent/pygments/cli.py
+++ b/fluent.pygments/fluent/pygments/cli.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function, unicode_literals
-
 import argparse
 import sys
 
diff --git a/fluent.pygments/fluent/pygments/lexer.py b/fluent.pygments/fluent/pygments/lexer.py
index 3e435c80..a3799095 100644
--- a/fluent.pygments/fluent/pygments/lexer.py
+++ b/fluent.pygments/fluent/pygments/lexer.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function, unicode_literals
-
 from fluent.syntax import ast as FTL
 from fluent.syntax import parse
 
@@ -38,7 +36,7 @@ def get_tokens_unprocessed(self, text):
 }
 
 
-class Tokenizer(object):
+class Tokenizer:
     def __init__(self, text):
         self.text = text
         self.ast = parse(text)
@@ -49,21 +47,18 @@ def tokenize(self, node=None):
         if isinstance(node, (FTL.Annotation, FTL.Span)):
             return
         if isinstance(node, FTL.SyntaxNode):
-            for token in self.tokenize_node(node):
-                yield token
+            yield from self.tokenize_node(node)
         elif isinstance(node, list):
             for child in node:
-                for token in self.tokenize(child):
-                    yield token
+                yield from self.tokenize(child)
 
     def tokenize_node(self, node):
         nodename = type(node).__name__
         if nodename in ATOMIC:
             yield self._token(node, ATOMIC[nodename])
         else:
-            tokenize = getattr(self, 'tokenize_{}'.format(nodename), self.generic_tokenize)
-            for token in tokenize(node):
-                yield token
+            tokenize = getattr(self, f'tokenize_{nodename}', self.generic_tokenize)
+            yield from tokenize(node)
 
     def generic_tokenize(self, node):
         children = [
@@ -74,13 +69,11 @@ def generic_tokenize(self, node):
             key=lambda child: child.span.start if isinstance(child, FTL.SyntaxNode) else child[0].span.start
         )
         for child in children:
-            for token in self.tokenize(child):
-                yield token
+            yield from self.tokenize(child)
 
     def tokenize_Variant(self, node):
         yield self._token(node.key, Token.Name.Attribute)
-        for token in self.tokenize(node.value):
-            yield token
+        yield from self.tokenize(node.value)
 
     def _token(self, node, token):
         return (
diff --git a/fluent.pygments/setup.cfg b/fluent.pygments/setup.cfg
index 4ffbb87b..3f66b813 100644
--- a/fluent.pygments/setup.cfg
+++ b/fluent.pygments/setup.cfg
@@ -1,5 +1,5 @@
 [metadata]
-version=1.0
+version=2.0
 
 [bdist_wheel]
 universal=1
@@ -17,7 +17,6 @@ not_skip=__init__.py
 install_requires =
   pygments
   fluent.syntax
-  six
 
 [options.entry_points]
 pygments.lexers =
diff --git a/fluent.pygments/setup.py b/fluent.pygments/setup.py
index caf59402..966a6fea 100755
--- a/fluent.pygments/setup.py
+++ b/fluent.pygments/setup.py
@@ -19,10 +19,12 @@
           'Development Status :: 3 - Alpha',
           'Intended Audience :: Developers',
           'License :: OSI Approved :: Apache Software License',
-          'Programming Language :: Python :: 2.7',
-          'Programming Language :: Python :: 3.5',
+          'Programming Language :: Python :: 3.6',
+          'Programming Language :: Python :: 3.7',
+          'Programming Language :: Python :: 3.8',
+          'Programming Language :: Python :: 3.9',
+          'Programming Language :: Python :: 3 :: Only',
       ],
       packages=['fluent', 'fluent.pygments'],
-      tests_require=['six'],
       test_suite='tests.pygments'
       )
diff --git a/fluent.runtime/CHANGELOG.rst b/fluent.runtime/CHANGELOG.rst
index 1cab74d1..8bf8016c 100644
--- a/fluent.runtime/CHANGELOG.rst
+++ b/fluent.runtime/CHANGELOG.rst
@@ -1,6 +1,12 @@
 Changelog
 =========
 
+fluent.runtime 0.4 (TBD)
+-----------------------------------
+
+* Drop support for Python 2.7 and 3.5
+* Add support for Python 3.6 through 3.9
+
 fluent.runtime 0.3.1 (May 20, 2020)
 -----------------------------------
 
diff --git a/fluent.runtime/fluent/runtime/__init__.py b/fluent.runtime/fluent/runtime/__init__.py
index d32e709c..60a8649e 100644
--- a/fluent.runtime/fluent/runtime/__init__.py
+++ b/fluent.runtime/fluent/runtime/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import babel
 import babel.numbers
 import babel.plural
@@ -28,7 +26,7 @@ def FluentResource(source):
     return parser.parse(source)
 
 
-class FluentBundle(object):
+class FluentBundle:
     """
     Bundles are single-language stores of translations.  They are
     aggregate parsed Fluent resources in the Fluent syntax and can
diff --git a/fluent.runtime/fluent/runtime/errors.py b/fluent.runtime/fluent/runtime/errors.py
index 5c25da42..1c718285 100644
--- a/fluent.runtime/fluent/runtime/errors.py
+++ b/fluent.runtime/fluent/runtime/errors.py
@@ -1,6 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
-
 class FluentFormatError(ValueError):
     def __eq__(self, other):
         return ((other.__class__ == self.__class__) and
diff --git a/fluent.runtime/fluent/runtime/fallback.py b/fluent.runtime/fluent/runtime/fallback.py
index b21fdee4..b25b4822 100644
--- a/fluent.runtime/fluent/runtime/fallback.py
+++ b/fluent.runtime/fluent/runtime/fallback.py
@@ -1,11 +1,8 @@
-# -*- coding: utf-8 -*-
-
 import codecs
 import os
-import six
 
 
-class FluentLocalization(object):
+class FluentLocalization:
     """
     Generic API for Fluent applications.
 
@@ -67,7 +64,7 @@ def _iterate_bundles(self):
                 yield bundle
 
 
-class AbstractResourceLoader(object):
+class AbstractResourceLoader:
     """
     Interface to implement for resource loaders.
     """
@@ -97,7 +94,7 @@ def __init__(self, roots):
         Create a resource loader. The roots may be a string for a single
         location on disk, or a list of strings.
         """
-        self.roots = [roots] if isinstance(roots, six.text_type) else roots
+        self.roots = [roots] if isinstance(roots, str) else roots
         from fluent.runtime import FluentResource
         self.Resource = FluentResource
 
diff --git a/fluent.runtime/fluent/runtime/prepare.py b/fluent.runtime/fluent/runtime/prepare.py
index c930236d..450745b8 100644
--- a/fluent.runtime/fluent/runtime/prepare.py
+++ b/fluent.runtime/fluent/runtime/prepare.py
@@ -1,9 +1,8 @@
-from __future__ import absolute_import, unicode_literals
 from fluent.syntax import ast as FTL
 from . import resolver
 
 
-class Compiler(object):
+class Compiler:
     def __call__(self, item):
         if isinstance(item, FTL.BaseNode):
             return self.compile(item)
diff --git a/fluent.runtime/fluent/runtime/resolver.py b/fluent.runtime/fluent/runtime/resolver.py
index ccafbb78..5394313d 100644
--- a/fluent.runtime/fluent/runtime/resolver.py
+++ b/fluent.runtime/fluent/runtime/resolver.py
@@ -1,9 +1,6 @@
-from __future__ import absolute_import, unicode_literals
-
 import contextlib
 
 import attr
-import six
 
 from fluent.syntax import ast as FTL
 from .errors import FluentCyclicReferenceError, FluentFormatError, FluentReferenceError
@@ -31,7 +28,7 @@
 
 
 @attr.s
-class CurrentEnvironment(object):
+class CurrentEnvironment:
     # The parts of ResolverEnvironment that we want to mutate (and restore)
     # temporarily for some parts of a call chain.
 
@@ -48,7 +45,7 @@ class CurrentEnvironment(object):
 
 
 @attr.s
-class ResolverEnvironment(object):
+class ResolverEnvironment:
     context = attr.ib()
     errors = attr.ib()
     part_count = attr.ib(default=0, init=False)
@@ -73,7 +70,7 @@ def modified_for_term_reference(self, args=None):
                              error_for_missing_arg=False)
 
 
-class BaseResolver(object):
+class BaseResolver:
     """
     Abstract base class of all partially evaluated resolvers.
 
@@ -104,13 +101,13 @@ def _fix_attributes(self):
 
 class Message(FTL.Message, EntryResolver):
     def __init__(self, id, **kwargs):
-        super(Message, self).__init__(id, **kwargs)
+        super().__init__(id, **kwargs)
         self._fix_attributes()
 
 
 class Term(FTL.Term, EntryResolver):
     def __init__(self, id, value, **kwargs):
-        super(Term, self).__init__(id, value, **kwargs)
+        super().__init__(id, value, **kwargs)
         self._fix_attributes()
 
 
@@ -119,7 +116,7 @@ class Pattern(FTL.Pattern, BaseResolver):
     MAX_PARTS = 1000
 
     def __init__(self, *args, **kwargs):
-        super(Pattern, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def __call__(self, env):
         if self in env.active_patterns:
@@ -130,7 +127,7 @@ def __call__(self, env):
         remaining_parts = self.MAX_PARTS - env.part_count
         if len(self.elements) > remaining_parts:
             env.active_patterns.remove(self)
-            raise ValueError("Too many parts in message (> {0}), "
+            raise ValueError("Too many parts in message (> {}), "
                              "aborting.".format(self.MAX_PARTS))
         retval = ''.join(
             resolve(element(env), env) for element in elements
@@ -143,7 +140,7 @@ def __call__(self, env):
 def resolve(fluentish, env):
     if isinstance(fluentish, FluentType):
         return fluentish.format(env.context._babel_locale)
-    if isinstance(fluentish, six.string_types):
+    if isinstance(fluentish, str):
         if len(fluentish) > MAX_PART_LENGTH:
             raise ValueError(
                 "Too many characters in placeable "
@@ -178,7 +175,7 @@ def __call__(self, env):
 
 class NumberLiteral(FTL.NumberLiteral, BaseResolver):
     def __init__(self, value, **kwargs):
-        super(NumberLiteral, self).__init__(value, **kwargs)
+        super().__init__(value, **kwargs)
         if '.' in self.value:
             self.value = FluentFloat(self.value)
         else:
@@ -200,7 +197,7 @@ def __call__(self, env):
         except LookupError:
             ref_id = reference_to_id(self)
             env.errors.append(unknown_reference_error_obj(ref_id))
-            return FluentNone('{{{}}}'.format(ref_id))
+            return FluentNone(f'{{{ref_id}}}')
 
 
 class MessageReference(FTL.MessageReference, EntryReference):
@@ -211,13 +208,13 @@ class TermReference(FTL.TermReference, EntryReference):
     def __call__(self, env):
         if self.arguments:
             if self.arguments.positional:
-                env.errors.append(FluentFormatError("Ignored positional arguments passed to term '{0}'"
+                env.errors.append(FluentFormatError("Ignored positional arguments passed to term '{}'"
                                                     .format(reference_to_id(self))))
             kwargs = {kwarg.name.name: kwarg.value(env) for kwarg in self.arguments.named}
         else:
             kwargs = None
         with env.modified_for_term_reference(args=kwargs):
-            return super(TermReference, self).__call__(env)
+            return super().__call__(env)
 
 
 class VariableReference(FTL.VariableReference, BaseResolver):
@@ -228,12 +225,12 @@ def __call__(self, env):
         except LookupError:
             if env.current.error_for_missing_arg:
                 env.errors.append(
-                    FluentReferenceError("Unknown external: {0}".format(name)))
+                    FluentReferenceError(f"Unknown external: {name}"))
             return FluentNone(name)
 
-        if isinstance(arg_val, (FluentType, six.text_type)):
+        if isinstance(arg_val, (FluentType, str)):
             return arg_val
-        env.errors.append(TypeError("Unsupported external type: {0}, {1}"
+        env.errors.append(TypeError("Unsupported external type: {}, {}"
                                     .format(name, type(arg_val))))
         return FluentNone(name)
 
@@ -303,7 +300,7 @@ def __call__(self, env):
         try:
             function = env.context._functions[function_name]
         except LookupError:
-            env.errors.append(FluentReferenceError("Unknown function: {0}"
+            env.errors.append(FluentReferenceError("Unknown function: {}"
                                                    .format(function_name)))
             return FluentNone(function_name + "()")
 
diff --git a/fluent.runtime/fluent/runtime/types.py b/fluent.runtime/fluent/runtime/types.py
index c8d26e38..c55df7bb 100644
--- a/fluent.runtime/fluent/runtime/types.py
+++ b/fluent.runtime/fluent/runtime/types.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import warnings
 from datetime import date, datetime
 from decimal import Decimal
@@ -13,39 +10,39 @@
 FORMAT_STYLE_DECIMAL = "decimal"
 FORMAT_STYLE_CURRENCY = "currency"
 FORMAT_STYLE_PERCENT = "percent"
-FORMAT_STYLE_OPTIONS = set([
+FORMAT_STYLE_OPTIONS = {
     FORMAT_STYLE_DECIMAL,
     FORMAT_STYLE_CURRENCY,
     FORMAT_STYLE_PERCENT,
-])
+}
 
 CURRENCY_DISPLAY_SYMBOL = "symbol"
 CURRENCY_DISPLAY_CODE = "code"
 CURRENCY_DISPLAY_NAME = "name"
-CURRENCY_DISPLAY_OPTIONS = set([
+CURRENCY_DISPLAY_OPTIONS = {
     CURRENCY_DISPLAY_SYMBOL,
     CURRENCY_DISPLAY_CODE,
     CURRENCY_DISPLAY_NAME,
-])
+}
 
-DATE_STYLE_OPTIONS = set([
+DATE_STYLE_OPTIONS = {
     "full",
     "long",
     "medium",
     "short",
     None,
-])
+}
 
-TIME_STYLE_OPTIONS = set([
+TIME_STYLE_OPTIONS = {
     "full",
     "long",
     "medium",
     "short",
     None,
-])
+}
 
 
-class FluentType(object):
+class FluentType:
     def format(self, locale):
         raise NotImplementedError()
 
@@ -62,7 +59,7 @@ def format(self, locale):
 
 
 @attr.s
-class NumberFormatOptions(object):
+class NumberFormatOptions:
     # We follow the Intl.NumberFormat parameter names here,
     # rather than using underscores as per PEP8, so that
     # we can stick to Fluent spec more easily.
@@ -88,7 +85,7 @@ class FluentNumber(FluentType):
     def __new__(cls,
                 value,
                 **kwargs):
-        self = super(FluentNumber, cls).__new__(cls, value)
+        self = super().__new__(cls, value)
         return self._init(value, kwargs)
 
     def _init(self, value, kwargs):
@@ -144,7 +141,7 @@ def replacer(s):
                 # https://github.com/python-babel/babel/issues/578 But it's
                 # better to display something than crash or a generic fallback
                 # string, so we just issue a warning and carry on for now.
-                warnings.warn("Unsupported currencyDisplayValue {0}, falling back to {1}"
+                warnings.warn("Unsupported currencyDisplayValue {}, falling back to {}"
                               .format(CURRENCY_DISPLAY_NAME,
                                       CURRENCY_DISPLAY_SYMBOL))
         if (self.options.minimumSignificantDigits is not None
@@ -228,7 +225,7 @@ def fluent_number(number, **kwargs):
     elif isinstance(number, FluentNone):
         return number
     else:
-        raise TypeError("Can't use fluent_number with object {0} for type {1}"
+        raise TypeError("Can't use fluent_number with object {} for type {}"
                         .format(number, type(number)))
 
 
@@ -247,7 +244,7 @@ def clone_pattern(pattern):
 
 
 @attr.s
-class DateFormatOptions(object):
+class DateFormatOptions:
     # Parameters.
     # See https://projectfluent.org/fluent/guide/functions.html#datetime
 
@@ -290,7 +287,7 @@ def _init_options(self, dt_obj, kwargs):
                                      kwargs)
         for k in kwargs:
             if k not in _SUPPORTED_DATETIME_OPTIONS:
-                warnings.warn("FluentDateType option {0} is not yet supported".format(k))
+                warnings.warn(f"FluentDateType option {k} is not yet supported")
 
     def format(self, locale):
         if isinstance(self, datetime):
@@ -359,5 +356,5 @@ def fluent_date(dt, **kwargs):
     elif isinstance(dt, FluentNone):
         return dt
     else:
-        raise TypeError("Can't use fluent_date with object {0} of type {1}"
+        raise TypeError("Can't use fluent_date with object {} of type {}"
                         .format(dt, type(dt)))
diff --git a/fluent.runtime/fluent/runtime/utils.py b/fluent.runtime/fluent/runtime/utils.py
index 84637f1b..56a61a35 100644
--- a/fluent.runtime/fluent/runtime/utils.py
+++ b/fluent.runtime/fluent/runtime/utils.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 from datetime import date, datetime
 from decimal import Decimal
 
@@ -53,7 +51,7 @@ def reference_to_id(ref):
 
 def unknown_reference_error_obj(ref_id):
     if ATTRIBUTE_SEPARATOR in ref_id:
-        return FluentReferenceError("Unknown attribute: {0}".format(ref_id))
+        return FluentReferenceError(f"Unknown attribute: {ref_id}")
     if ref_id.startswith(TERM_SIGIL):
-        return FluentReferenceError("Unknown term: {0}".format(ref_id))
-    return FluentReferenceError("Unknown message: {0}".format(ref_id))
+        return FluentReferenceError(f"Unknown term: {ref_id}")
+    return FluentReferenceError(f"Unknown message: {ref_id}")
diff --git a/fluent.runtime/setup.cfg b/fluent.runtime/setup.cfg
index 3844d0bf..d7fea75c 100644
--- a/fluent.runtime/setup.cfg
+++ b/fluent.runtime/setup.cfg
@@ -1,5 +1,5 @@
 [metadata]
-version=0.3.1
+version=0.4
 
 [bdist_wheel]
 universal=1
diff --git a/fluent.runtime/setup.py b/fluent.runtime/setup.py
index 28f86872..c52f8634 100755
--- a/fluent.runtime/setup.py
+++ b/fluent.runtime/setup.py
@@ -20,8 +20,11 @@
           'Development Status :: 3 - Alpha',
           'Intended Audience :: Developers',
           'License :: OSI Approved :: Apache Software License',
-          'Programming Language :: Python :: 2.7',
-          'Programming Language :: Python :: 3.5',
+          'Programming Language :: Python :: 3.6',
+          'Programming Language :: Python :: 3.7',
+          'Programming Language :: Python :: 3.8',
+          'Programming Language :: Python :: 3.9',
+          'Programming Language :: Python :: 3 :: Only',
       ],
       packages=['fluent', 'fluent.runtime'],
       # These should also be duplicated in tox.ini and /.github/workflows/fluent.runtime.yml
@@ -33,7 +36,4 @@
           'six',
       ],
       test_suite='tests',
-      tests_require=[
-          'mock',
-      ],
       )
diff --git a/fluent.runtime/tools/benchmarks/fluent_benchmark.py b/fluent.runtime/tools/benchmarks/fluent_benchmark.py
index 99eff5f2..6541381c 100644
--- a/fluent.runtime/tools/benchmarks/fluent_benchmark.py
+++ b/fluent.runtime/tools/benchmarks/fluent_benchmark.py
@@ -5,7 +5,6 @@
 
 import sys
 import pytest
-import six
 
 from fluent.runtime import FluentBundle, FluentResource
 
diff --git a/fluent.runtime/tox.ini b/fluent.runtime/tox.ini
index e494f7a2..26901620 100644
--- a/fluent.runtime/tox.ini
+++ b/fluent.runtime/tox.ini
@@ -1,7 +1,7 @@
 # This config is for local testing.
 # It should be correspond to .github/workflows/fluent.runtime.yml
 [tox]
-envlist = {py27,py35,py36,py37,pypy,pypy3}-syntax0.17, py36-syntax0.17, latest
+envlist = {py36,py37,py38,py39,pypy3}-syntax, py3-syntax0.17, latest
 skipsdist=True
 
 [testenv]
@@ -11,13 +11,13 @@ deps =
      syntax0.17: fluent.syntax==0.17
      attrs==19.1.0
      babel==2.7.0
-     mock==3.0.5
      pytz==2019.2
      six==1.12.0
+     syntax: .
 commands = ./runtests.py
 
 [testenv:latest]
-basepython = python3.7
+basepython = python3
 deps =
      .
-     mock
+     six


Test patch:
diff --git a/fluent.pygments/tests/pygments/test_lexer.py b/fluent.pygments/tests/pygments/test_lexer.py
index 036158a4..13ee3cf6 100644
--- a/fluent.pygments/tests/pygments/test_lexer.py
+++ b/fluent.pygments/tests/pygments/test_lexer.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function, unicode_literals
-
 import unittest
 from pygments.token import Token
 
diff --git a/fluent.runtime/tests/format/test_arguments.py b/fluent.runtime/tests/format/test_arguments.py
index ae2b5118..543c3bca 100644
--- a/fluent.runtime/tests/format/test_arguments.py
+++ b/fluent.runtime/tests/format/test_arguments.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/format/test_attributes.py b/fluent.runtime/tests/format/test_attributes.py
index e36a5257..4984b4b4 100644
--- a/fluent.runtime/tests/format/test_attributes.py
+++ b/fluent.runtime/tests/format/test_attributes.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/format/test_builtins.py b/fluent.runtime/tests/format/test_builtins.py
index 487e05d1..bcdd1b6c 100644
--- a/fluent.runtime/tests/format/test_builtins.py
+++ b/fluent.runtime/tests/format/test_builtins.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 from datetime import date, datetime
 from decimal import Decimal
diff --git a/fluent.runtime/tests/format/test_functions.py b/fluent.runtime/tests/format/test_functions.py
index 8d4b5fab..f427bb4b 100644
--- a/fluent.runtime/tests/format/test_functions.py
+++ b/fluent.runtime/tests/format/test_functions.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/format/test_isolating.py b/fluent.runtime/tests/format/test_isolating.py
index a2c86d4c..ddbcdb44 100644
--- a/fluent.runtime/tests/format/test_isolating.py
+++ b/fluent.runtime/tests/format/test_isolating.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/format/test_parameterized_terms.py b/fluent.runtime/tests/format/test_parameterized_terms.py
index 2caf2338..16c0ea86 100644
--- a/fluent.runtime/tests/format/test_parameterized_terms.py
+++ b/fluent.runtime/tests/format/test_parameterized_terms.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/format/test_placeables.py b/fluent.runtime/tests/format/test_placeables.py
index e6d4d2f4..896ac75e 100644
--- a/fluent.runtime/tests/format/test_placeables.py
+++ b/fluent.runtime/tests/format/test_placeables.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/format/test_primitives.py b/fluent.runtime/tests/format/test_primitives.py
index 913fac2b..5e183f40 100644
--- a/fluent.runtime/tests/format/test_primitives.py
+++ b/fluent.runtime/tests/format/test_primitives.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/format/test_select_expression.py b/fluent.runtime/tests/format/test_select_expression.py
index a62d1551..0101da54 100644
--- a/fluent.runtime/tests/format/test_select_expression.py
+++ b/fluent.runtime/tests/format/test_select_expression.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/test_bomb.py b/fluent.runtime/tests/test_bomb.py
index 8c00c740..d6331ab4 100644
--- a/fluent.runtime/tests/test_bomb.py
+++ b/fluent.runtime/tests/test_bomb.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/test_bundle.py b/fluent.runtime/tests/test_bundle.py
index 61e3b5ec..19bf9612 100644
--- a/fluent.runtime/tests/test_bundle.py
+++ b/fluent.runtime/tests/test_bundle.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 
 from fluent.runtime import FluentBundle, FluentResource
diff --git a/fluent.runtime/tests/test_fallback.py b/fluent.runtime/tests/test_fallback.py
index af95bf2a..69da1120 100644
--- a/fluent.runtime/tests/test_fallback.py
+++ b/fluent.runtime/tests/test_fallback.py
@@ -1,10 +1,7 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
 import unittest
-import mock
+from unittest import mock
+import io
 import os
-import six
 
 from fluent.runtime import (
     FluentLocalization,
@@ -33,7 +30,7 @@ def test_bundles(self, codecs_open, isfile):
             'en/two.ftl': 'five = exists',
         }
         isfile.side_effect = lambda p: p in data or ISFILE(p)
-        codecs_open.side_effect = lambda p, _, __: six.StringIO(data[p])
+        codecs_open.side_effect = lambda p, _, __: io.StringIO(data[p])
         l10n = FluentLocalization(
             ['de', 'fr', 'en'],
             ['one.ftl', 'two.ftl'],
@@ -67,7 +64,7 @@ def test_all_exist(self, codecs_open, isfile):
             'en/two.ftl': 'two = exists',
         }
         isfile.side_effect = lambda p: p in data
-        codecs_open.side_effect = lambda p, _, __: six.StringIO(data[p])
+        codecs_open.side_effect = lambda p, _, __: io.StringIO(data[p])
         loader = FluentResourceLoader('{locale}')
         resources_list = list(loader.resources('en', ['one.ftl', 'two.ftl']))
         self.assertEqual(len(resources_list), 1)
@@ -79,7 +76,7 @@ def test_one_exists(self, codecs_open, isfile):
             'en/two.ftl': 'two = exists',
         }
         isfile.side_effect = lambda p: p in data
-        codecs_open.side_effect = lambda p, _, __: six.StringIO(data[p])
+        codecs_open.side_effect = lambda p, _, __: io.StringIO(data[p])
         loader = FluentResourceLoader('{locale}')
         resources_list = list(loader.resources('en', ['one.ftl', 'two.ftl']))
         self.assertEqual(len(resources_list), 1)
@@ -89,7 +86,7 @@ def test_one_exists(self, codecs_open, isfile):
     def test_none_exist(self, codecs_open, isfile):
         data = {}
         isfile.side_effect = lambda p: p in data
-        codecs_open.side_effect = lambda p, _, __: six.StringIO(data[p])
+        codecs_open.side_effect = lambda p, _, __: io.StringIO(data[p])
         loader = FluentResourceLoader('{locale}')
         resources_list = list(loader.resources('en', ['one.ftl', 'two.ftl']))
         self.assertEqual(len(resources_list), 0)
diff --git a/fluent.runtime/tests/test_types.py b/fluent.runtime/tests/test_types.py
index 8e8a2dd4..b1272bfe 100644
--- a/fluent.runtime/tests/test_types.py
+++ b/fluent.runtime/tests/test_types.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
 import unittest
 import warnings
 from datetime import date, datetime
diff --git a/fluent.runtime/tests/utils.py b/fluent.runtime/tests/utils.py
index 5bb38076..56301df6 100644
--- a/fluent.runtime/tests/utils.py
+++ b/fluent.runtime/tests/utils.py
@@ -1,7 +1,5 @@
-from __future__ import unicode_literals
-
 import textwrap
 
 
 def dedent_ftl(text):
-    return textwrap.dedent("{}\n".format(text.rstrip()))
+    return textwrap.dedent(f"{text.rstrip()}\n")