diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..eb54a96
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: asottile
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cdc0d1c..f235a1e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v2.5.0
+    rev: v4.0.1
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer
@@ -9,30 +9,30 @@ repos:
     -   id: debug-statements
     -   id: name-tests-test
     -   id: requirements-txt-fixer
--   repo: https://gitlab.com/pycqa/flake8
-    rev: 3.8.0
+-   repo: https://github.com/PyCQA/flake8
+    rev: 3.9.2
     hooks:
     -   id: flake8
 -   repo: https://github.com/pre-commit/mirrors-autopep8
-    rev: v1.5.2
+    rev: v1.5.7
     hooks:
     -   id: autopep8
 -   repo: https://github.com/asottile/reorder_python_imports
-    rev: v2.3.0
+    rev: v2.5.0
     hooks:
     -   id: reorder-python-imports
         args: [--py3-plus]
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v2.4.1
+    rev: v2.16.0
     hooks:
     -   id: pyupgrade
         args: [--py36-plus]
 -   repo: https://github.com/asottile/add-trailing-comma
-    rev: v2.0.1
+    rev: v2.1.0
     hooks:
     -   id: add-trailing-comma
         args: [--py36-plus]
 -   repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v1.9.0
+    rev: v1.17.0
     hooks:
     -   id: setup-cfg-fmt
diff --git a/README.md b/README.md
index 2b8f227..3d9745a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
 [![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/asottile.cfgv?branchName=master)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=24&branchName=master)
 [![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/24/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=24&branchName=master)
+[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/cfgv/master.svg)](https://results.pre-commit.ci/latest/github/asottile/cfgv/master)
 
 cfgv
 ====
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index ebca7d2..61a5a11 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -10,10 +10,9 @@ resources:
       type: github
       endpoint: github
       name: asottile/azure-pipeline-templates
-      ref: refs/tags/v1.0.1
+      ref: refs/tags/v2.1.0
 
 jobs:
-- template: job--pre-commit.yml@asottile
 - template: job--python-tox.yml@asottile
   parameters:
     toxenvs: [pypy3, py36, py37, py38]
diff --git a/cfgv.py b/cfgv.py
index f0b9eff..2a9ceab 100644
--- a/cfgv.py
+++ b/cfgv.py
@@ -392,8 +392,8 @@ def load_from_filename(
         exc_tp=ValidationError,
 ):
     with reraise_as(exc_tp):
-        if not os.path.exists(filename):
-            raise ValidationError(f'{filename} does not exist')
+        if not os.path.isfile(filename):
+            raise ValidationError(f'{filename} is not a file')
 
         with validate_context(f'File {filename}'):
             try:
diff --git a/setup.cfg b/setup.cfg
index cef13f1..9a92ddf 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = cfgv
-version = 3.2.0
+version = 3.3.0
 description = Validate configuration and produce human readable error messages.
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -16,6 +16,7 @@ classifiers =
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
     Programming Language :: Python :: Implementation :: CPython
     Programming Language :: Python :: Implementation :: PyPy
 
diff --git a/tests/cfgv_test.py b/tests/cfgv_test.py
index 7320e4c..15bf581 100644
--- a/tests/cfgv_test.py
+++ b/tests/cfgv_test.py
@@ -529,7 +529,15 @@ class Error(Exception):
 def test_load_from_filename_file_does_not_exist():
     with pytest.raises(Error) as excinfo:
         load_from_filename('does_not_exist', map_required, json.loads, Error)
-    assert excinfo.value.args[0].error_msg == 'does_not_exist does not exist'
+    assert excinfo.value.args[0].error_msg == 'does_not_exist is not a file'
+
+
+def test_load_from_filename_not_a_file(tmpdir):
+    with tmpdir.as_cwd():
+        tmpdir.join('f').ensure_dir()
+        with pytest.raises(Error) as excinfo:
+            load_from_filename('f', map_required, json.loads, Error)
+        assert excinfo.value.args[0].error_msg == 'f is not a file'
 
 
 def test_load_from_filename_unicode_error(tmp_path):