diff --git a/.isort.cfg b/.isort.cfg
index 0cece53b..ba2778dc 100644
--- a/.isort.cfg
+++ b/.isort.cfg
@@ -1,3 +1,6 @@
[settings]
multi_line_output=3
-
+include_trailing_comma=True
+force_grid_wrap=0
+use_parentheses=True
+line_length=88
diff --git a/datasette/__init__.py b/datasette/__init__.py
index 0e59760a..a7f79e54 100644
--- a/datasette/__init__.py
+++ b/datasette/__init__.py
@@ -1,3 +1,4 @@
-from datasette.version import __version_info__, __version__ # noqa
+from datasette.version import __version__, __version_info__ # noqa
+
from .hookspecs import hookimpl # noqa
from .hookspecs import hookspec # noqa
diff --git a/datasette/app.py b/datasette/app.py
index 4a8ead1d..ad661bd7 100644
--- a/datasette/app.py
+++ b/datasette/app.py
@@ -10,17 +10,13 @@ from concurrent import futures
from pathlib import Path
import click
-from markupsafe import Markup
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
+from markupsafe import Markup
-from .views.base import DatasetteError, ureg, AsgiRouter
-from .views.database import DatabaseDownload, DatabaseView
-from .views.index import IndexView
-from .views.special import JsonDataView
-from .views.table import RowView, TableView
-from .renderer import json_renderer
from .database import Database
-
+from .plugins import DEFAULT_PLUGINS, pm
+from .renderer import json_renderer
+from .tracer import AsgiTracer, trace
from .utils import (
QueryInterrupted,
Results,
@@ -35,15 +31,18 @@ from .utils import (
from .utils.asgi import (
AsgiLifespan,
NotFound,
- asgi_static,
asgi_send,
asgi_send_html,
asgi_send_json,
asgi_send_redirect,
+ asgi_static,
)
-from .tracer import trace, AsgiTracer
-from .plugins import pm, DEFAULT_PLUGINS
from .version import __version__
+from .views.base import AsgiRouter, DatasetteError, ureg
+from .views.database import DatabaseDownload, DatabaseView
+from .views.index import IndexView
+from .views.special import JsonDataView
+from .views.table import RowView, TableView
app_root = Path(__file__).parent.parent
diff --git a/datasette/cli.py b/datasette/cli.py
index 181b281c..adcb07d9 100644
--- a/datasette/cli.py
+++ b/datasette/cli.py
@@ -1,19 +1,21 @@
import asyncio
-import uvicorn
-import click
-from click import formatting
-from click_default_group import DefaultGroup
import json
import os
import shutil
-from subprocess import call
import sys
-from .app import Datasette, DEFAULT_CONFIG, CONFIG_OPTIONS, pm
+from subprocess import call
+
+import click
+import uvicorn
+from click import formatting
+from click_default_group import DefaultGroup
+
+from .app import CONFIG_OPTIONS, DEFAULT_CONFIG, Datasette, pm
from .utils import (
- temporary_docker_directory,
- value_as_boolean,
StaticMount,
ValueAsBooleanError,
+ temporary_docker_directory,
+ value_as_boolean,
)
diff --git a/datasette/database.py b/datasette/database.py
index e4915770..3e464b23 100644
--- a/datasette/database.py
+++ b/datasette/database.py
@@ -1,5 +1,6 @@
from pathlib import Path
+from .inspect import inspect_hash
from .utils import (
QueryInterrupted,
detect_fts,
@@ -10,7 +11,6 @@ from .utils import (
sqlite3,
table_columns,
)
-from .inspect import inspect_hash
class Database:
diff --git a/datasette/facets.py b/datasette/facets.py
index 76d73e51..f1614c0d 100644
--- a/datasette/facets.py
+++ b/datasette/facets.py
@@ -1,14 +1,15 @@
import json
-import urllib
import re
+import urllib
+
from datasette import hookimpl
from datasette.utils import (
+ InvalidSql,
+ QueryInterrupted,
+ detect_json1,
escape_sqlite,
path_with_added_args,
path_with_removed_args,
- detect_json1,
- QueryInterrupted,
- InvalidSql,
sqlite3,
)
diff --git a/datasette/hookspecs.py b/datasette/hookspecs.py
index 61523a31..7273ebf0 100644
--- a/datasette/hookspecs.py
+++ b/datasette/hookspecs.py
@@ -1,5 +1,4 @@
-from pluggy import HookimplMarker
-from pluggy import HookspecMarker
+from pluggy import HookimplMarker, HookspecMarker
hookspec = HookspecMarker("datasette")
hookimpl = HookimplMarker("datasette")
diff --git a/datasette/inspect.py b/datasette/inspect.py
index 2324c02c..aeb28624 100644
--- a/datasette/inspect.py
+++ b/datasette/inspect.py
@@ -1,16 +1,15 @@
import hashlib
from .utils import (
- detect_spatialite,
detect_fts,
detect_primary_keys,
+ detect_spatialite,
escape_sqlite,
get_all_foreign_keys,
- table_columns,
sqlite3,
+ table_columns,
)
-
HASH_BLOCK_SIZE = 1024 * 1024
diff --git a/datasette/plugins.py b/datasette/plugins.py
index bf3735dc..6043985f 100644
--- a/datasette/plugins.py
+++ b/datasette/plugins.py
@@ -1,6 +1,8 @@
import importlib
-import pluggy
import sys
+
+import pluggy
+
from . import hookspecs
DEFAULT_PLUGINS = (
diff --git a/datasette/publish/cloudrun.py b/datasette/publish/cloudrun.py
index 436b5d2b..153bb86e 100644
--- a/datasette/publish/cloudrun.py
+++ b/datasette/publish/cloudrun.py
@@ -1,13 +1,15 @@
-from datasette import hookimpl
-import click
import json
from subprocess import check_call, check_output
+import click
+
+from datasette import hookimpl
+
+from ..utils import temporary_docker_directory
from .common import (
add_common_publish_arguments_and_options,
fail_if_publish_binary_not_installed,
)
-from ..utils import temporary_docker_directory
@hookimpl
diff --git a/datasette/publish/common.py b/datasette/publish/common.py
index a31eef02..6b748b8b 100644
--- a/datasette/publish/common.py
+++ b/datasette/publish/common.py
@@ -1,8 +1,10 @@
-from ..utils import StaticMount
-import click
import shutil
import sys
+import click
+
+from ..utils import StaticMount
+
def add_common_publish_arguments_and_options(subcommand):
for decorator in reversed(
diff --git a/datasette/publish/heroku.py b/datasette/publish/heroku.py
index 5705500f..c1553c29 100644
--- a/datasette/publish/heroku.py
+++ b/datasette/publish/heroku.py
@@ -1,17 +1,19 @@
-from contextlib import contextmanager
-from datasette import hookimpl
-import click
import json
import os
import shlex
-from subprocess import call, check_output
import tempfile
+from contextlib import contextmanager
+from subprocess import call, check_output
+
+import click
+
+from datasette import hookimpl
+from datasette.utils import link_or_copy, link_or_copy_directory
from .common import (
add_common_publish_arguments_and_options,
fail_if_publish_binary_not_installed,
)
-from datasette.utils import link_or_copy, link_or_copy_directory
@hookimpl
diff --git a/datasette/publish/now.py b/datasette/publish/now.py
index 38add86e..a6cb6081 100644
--- a/datasette/publish/now.py
+++ b/datasette/publish/now.py
@@ -1,13 +1,15 @@
-from datasette import hookimpl
-import click
import json
-from subprocess import run, PIPE
+from subprocess import PIPE, run
+import click
+
+from datasette import hookimpl
+
+from ..utils import temporary_docker_directory
from .common import (
add_common_publish_arguments_and_options,
fail_if_publish_binary_not_installed,
)
-from ..utils import temporary_docker_directory
@hookimpl
diff --git a/datasette/renderer.py b/datasette/renderer.py
index 349c2922..326c4480 100644
--- a/datasette/renderer.py
+++ b/datasette/renderer.py
@@ -1,9 +1,10 @@
import json
+
from datasette.utils import (
- value_as_boolean,
- remove_infinites,
CustomJSONEncoder,
path_from_row_pks,
+ remove_infinites,
+ value_as_boolean,
)
diff --git a/datasette/tracer.py b/datasette/tracer.py
index e46a6fda..54779e71 100644
--- a/datasette/tracer.py
+++ b/datasette/tracer.py
@@ -1,8 +1,8 @@
import asyncio
-from contextlib import contextmanager
-import time
import json
+import time
import traceback
+from contextlib import contextmanager
tracers = {}
diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py
index 94ccc23e..c8f8049e 100644
--- a/datasette/utils/__init__.py
+++ b/datasette/utils/__init__.py
@@ -1,19 +1,20 @@
-from contextlib import contextmanager
-from collections import OrderedDict
import base64
-import click
import hashlib
import imp
import json
+import numbers
import os
-import pkg_resources
import re
import shlex
+import shutil
import tempfile
import time
-import shutil
import urllib
-import numbers
+from collections import OrderedDict
+from contextlib import contextmanager
+
+import click
+import pkg_resources
try:
import pysqlite3 as sqlite3
diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py
index fdf330ae..3dc14c40 100644
--- a/datasette/utils/asgi.py
+++ b/datasette/utils/asgi.py
@@ -1,12 +1,14 @@
import json
-from datasette.utils import RequestParameters
-from mimetypes import guess_type
-from urllib.parse import parse_qs, urlunparse
-from pathlib import Path
-from html import escape
import re
+from html import escape
+from mimetypes import guess_type
+from pathlib import Path
+from urllib.parse import parse_qs, urlunparse
+
import aiofiles
+from datasette.utils import RequestParameters
+
class NotFound(Exception):
pass
diff --git a/datasette/views/base.py b/datasette/views/base.py
index 7acb7304..c6a61109 100644
--- a/datasette/views/base.py
+++ b/datasette/views/base.py
@@ -4,32 +4,31 @@ import itertools
import re
import time
import urllib
+from html import escape
import jinja2
import pint
-from html import escape
-
from datasette import __version__
from datasette.plugins import pm
from datasette.utils import (
- QueryInterrupted,
InvalidSql,
LimitedWriter,
+ QueryInterrupted,
format_bytes,
is_url,
path_with_added_args,
- path_with_removed_args,
path_with_format,
+ path_with_removed_args,
resolve_table_and_format,
sqlite3,
to_css_class,
)
from datasette.utils.asgi import (
- AsgiStream,
- AsgiWriter,
AsgiRouter,
+ AsgiStream,
AsgiView,
+ AsgiWriter,
NotFound,
Response,
)
diff --git a/datasette/views/index.py b/datasette/views/index.py
index 2c1c017a..a0440520 100644
--- a/datasette/views/index.py
+++ b/datasette/views/index.py
@@ -7,7 +7,6 @@ from datasette.version import __version__
from .base import BaseView
-
# Truncate table list on homepage at:
TRUNCATE_AT = 5
diff --git a/datasette/views/special.py b/datasette/views/special.py
index c4976bb2..69ae344a 100644
--- a/datasette/views/special.py
+++ b/datasette/views/special.py
@@ -1,5 +1,7 @@
import json
+
from datasette.utils.asgi import Response
+
from .base import BaseView
diff --git a/datasette/views/table.py b/datasette/views/table.py
index 06be5671..36a9b1be 100644
--- a/datasette/views/table.py
+++ b/datasette/views/table.py
@@ -1,9 +1,10 @@
-import urllib
import itertools
import json
+import urllib
import jinja2
+from datasette.filters import Filters
from datasette.plugins import pm
from datasette.utils import (
CustomRow,
@@ -24,8 +25,8 @@ from datasette.utils import (
value_as_boolean,
)
from datasette.utils.asgi import NotFound
-from datasette.filters import Filters
-from .base import DataView, DatasetteError, ureg
+
+from .base import DatasetteError, DataView, ureg
LINK_WITH_LABEL = (
'{label} {id}'
diff --git a/docs/conf.py b/docs/conf.py
index d369e4f5..49e71be6 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -164,6 +164,3 @@ texinfo_documents = [
author, 'Datasette', 'One line description of project.',
'Miscellaneous'),
]
-
-
-
diff --git a/setup.py b/setup.py
index f66d03da..9e2fffee 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,8 @@
-from setuptools import setup, find_packages
import os
import sys
+from setuptools import find_packages, setup
+
import versioneer
@@ -62,6 +63,7 @@ setup(
"aiohttp==3.5.3",
"beautifulsoup4==4.6.1",
"asgiref==3.1.2",
+ "isort==4.3.20",
]
+ maybe_black
},
diff --git a/tests/fixtures.py b/tests/fixtures.py
index 00140f50..ac5efe0e 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -1,19 +1,21 @@
-from datasette.app import Datasette
-from datasette.utils import sqlite3
-from asgiref.testing import ApplicationCommunicator
-from asgiref.sync import async_to_sync
import itertools
import json
import os
import pathlib
-import pytest
import random
-import sys
import string
+import sys
import tempfile
import time
from urllib.parse import unquote
+import pytest
+from asgiref.sync import async_to_sync
+from asgiref.testing import ApplicationCommunicator
+
+from datasette.app import Datasette
+from datasette.utils import sqlite3
+
class TestResponse:
def __init__(self, status, headers, body):
diff --git a/tests/test_api.py b/tests/test_api.py
index a32ed5e3..50f01b27 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,22 +1,25 @@
+import json
+import urllib
+
+import pytest
+
from datasette.utils import detect_json1
+
from .fixtures import ( # noqa
+ METADATA,
app_client,
- app_client_no_files,
- app_client_with_hash,
- app_client_shorter_time_limit,
app_client_larger_cache_size,
+ app_client_no_files,
app_client_returned_rows_matches_page_size,
+ app_client_shorter_time_limit,
app_client_two_attached_databases_one_immutable,
app_client_with_cors,
app_client_with_dot,
+ app_client_with_hash,
generate_compound_rows,
generate_sortable_rows,
make_app_client,
- METADATA,
)
-import json
-import pytest
-import urllib
def test_homepage(app_client):
diff --git a/tests/test_cli.py b/tests/test_cli.py
index d1ab6522..72b720b3 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,8 +1,11 @@
-from .fixtures import app_client, make_app_client
-from datasette.cli import cli
-from click.testing import CliRunner
-import pathlib
import json
+import pathlib
+
+from click.testing import CliRunner
+
+from datasette.cli import cli
+
+from .fixtures import app_client, make_app_client
def test_inspect_cli(app_client):
diff --git a/tests/test_database.py b/tests/test_database.py
index f9a31993..d9cd7d7d 100644
--- a/tests/test_database.py
+++ b/tests/test_database.py
@@ -1,6 +1,7 @@
-from .fixtures import app_client
import pytest
+from .fixtures import app_client
+
@pytest.mark.parametrize(
"tables,exists",
diff --git a/tests/test_docs.py b/tests/test_docs.py
index d481a633..979411c9 100644
--- a/tests/test_docs.py
+++ b/tests/test_docs.py
@@ -1,13 +1,15 @@
"""
Tests to ensure certain things are documented.
"""
+import re
+from pathlib import Path
+
+import pytest
from click.testing import CliRunner
+
from datasette import app
from datasette.cli import cli
from datasette.filters import Filters
-from pathlib import Path
-import pytest
-import re
docs_path = Path(__file__).parent.parent / "docs"
label_re = re.compile(r"\.\. _([^\s:]+):")
diff --git a/tests/test_facets.py b/tests/test_facets.py
index b1037396..b483a914 100644
--- a/tests/test_facets.py
+++ b/tests/test_facets.py
@@ -1,9 +1,12 @@
-from datasette.facets import ColumnFacet, ArrayFacet, DateFacet, ManyToManyFacet
+from collections import namedtuple
+
+import pytest
+
+from datasette.facets import ArrayFacet, ColumnFacet, DateFacet, ManyToManyFacet
from datasette.utils import detect_json1
+
from .fixtures import app_client # noqa
from .utils import MockRequest
-from collections import namedtuple
-import pytest
@pytest.mark.asyncio
diff --git a/tests/test_filters.py b/tests/test_filters.py
index fd682cd9..fca4ca79 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -1,6 +1,7 @@
-from datasette.filters import Filters
import pytest
+from datasette.filters import Filters
+
@pytest.mark.parametrize(
"args,expected_where,expected_params",
diff --git a/tests/test_html.py b/tests/test_html.py
index 32fa2fe3..8892a177 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -1,17 +1,19 @@
+import json
+import pathlib
+import re
+import urllib.parse
+
+import pytest
from bs4 import BeautifulSoup as Soup
+
from .fixtures import ( # noqa
+ METADATA,
app_client,
app_client_shorter_time_limit,
app_client_two_attached_databases,
app_client_with_hash,
make_app_client,
- METADATA,
)
-import json
-import pathlib
-import pytest
-import re
-import urllib.parse
def test_homepage(app_client_two_attached_databases):
diff --git a/tests/test_black.py b/tests/test_lint.py
similarity index 50%
rename from tests/test_black.py
rename to tests/test_lint.py
index 68e2dcc0..69c8ab5d 100644
--- a/tests/test_black.py
+++ b/tests/test_lint.py
@@ -1,7 +1,10 @@
-from click.testing import CliRunner
-from pathlib import Path
-import pytest
+import io
import sys
+from pathlib import Path
+
+import isort
+import pytest
+from click.testing import CliRunner
code_root = Path(__file__).parent.parent
@@ -18,3 +21,18 @@ def test_black():
black.main, [str(code_root / "tests"), str(code_root / "datasette"), "--check"]
)
assert result.exit_code == 0, result.output
+
+
+@pytest.mark.parametrize(
+ "path",
+ list(code_root.glob("tests/**/*.py")) + list(code_root.glob("datasette/**/*.py")),
+)
+def test_isort(path):
+ # Have to capture stdout because isort uses print() directly
+ stdout = sys.stdout
+ sys.stdout = io.StringIO()
+ result = isort.SortImports(path, check=True)
+ assert (
+ not result.incorrectly_sorted
+ ), "{} has incorrectly sorted imports, fix with 'isort -rc tests && isort -rc datasette && black tests datasette'"
+ sys.stdout = stdout
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index 56033bdd..2bc01098 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -1,11 +1,13 @@
-from bs4 import BeautifulSoup as Soup
-from .fixtures import app_client # noqa
import base64
import json
import re
-import pytest
import urllib
+import pytest
+from bs4 import BeautifulSoup as Soup
+
+from .fixtures import app_client # noqa
+
def test_plugins_dir_plugin(app_client):
response = app_client.get(
diff --git a/tests/test_publish_cloudrun.py b/tests/test_publish_cloudrun.py
index d26786ce..046d0b22 100644
--- a/tests/test_publish_cloudrun.py
+++ b/tests/test_publish_cloudrun.py
@@ -1,7 +1,9 @@
-from click.testing import CliRunner
-from datasette import cli
from unittest import mock
+from click.testing import CliRunner
+
+from datasette import cli
+
@mock.patch("shutil.which")
def test_publish_cloudrun_requires_gcloud(mock_which):
diff --git a/tests/test_publish_heroku.py b/tests/test_publish_heroku.py
index 08fdeaea..e3385844 100644
--- a/tests/test_publish_heroku.py
+++ b/tests/test_publish_heroku.py
@@ -1,7 +1,9 @@
-from click.testing import CliRunner
-from datasette import cli
from unittest import mock
+from click.testing import CliRunner
+
+from datasette import cli
+
@mock.patch("shutil.which")
def test_publish_heroku_requires_heroku(mock_which):
diff --git a/tests/test_publish_now.py b/tests/test_publish_now.py
index fa1ab30a..c2df9334 100644
--- a/tests/test_publish_now.py
+++ b/tests/test_publish_now.py
@@ -1,7 +1,9 @@
-from click.testing import CliRunner
-from datasette import cli
-from unittest import mock
import subprocess
+from unittest import mock
+
+from click.testing import CliRunner
+
+from datasette import cli
@mock.patch("shutil.which")
diff --git a/tests/test_utils.py b/tests/test_utils.py
index e9e722b8..ee87ae36 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -2,16 +2,18 @@
Tests for various datasette helper functions.
"""
-from datasette import utils
-from datasette.utils.asgi import Request
-from datasette.filters import Filters
import json
import os
-import pytest
import sqlite3
import tempfile
from unittest.mock import patch
+import pytest
+
+from datasette import utils
+from datasette.filters import Filters
+from datasette.utils.asgi import Request
+
@pytest.mark.parametrize(
"path,expected",
diff --git a/update-docs-help.py b/update-docs-help.py
index 3a1eb860..5a7d85a2 100644
--- a/update-docs-help.py
+++ b/update-docs-help.py
@@ -1,7 +1,9 @@
-from click.testing import CliRunner
-from datasette.cli import cli
from pathlib import Path
+from click.testing import CliRunner
+
+from datasette.cli import cli
+
docs_path = Path(__file__).parent / "docs"
includes = (
diff --git a/versioneer.py b/versioneer.py
index 64fea1c8..dffd66b6 100644
--- a/versioneer.py
+++ b/versioneer.py
@@ -277,10 +277,7 @@ https://creativecommons.org/publicdomain/zero/1.0/ .
"""
from __future__ import print_function
-try:
- import configparser
-except ImportError:
- import ConfigParser as configparser
+
import errno
import json
import os
@@ -288,6 +285,11 @@ import re
import subprocess
import sys
+try:
+ import configparser
+except ImportError:
+ import ConfigParser as configparser
+
class VersioneerConfig:
"""Container for Versioneer configuration parameters."""