diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index 9bd2c0a..8ea7a2c 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -54,7 +54,7 @@ jobs:
args: check --target-version ${{ env.PYTHON_TARGET }}
- name: Tests and coverage
- run: hatch run cov
+ run: hatch test -p -c
- name: Codecov - Upload coverage
uses: codecov/codecov-action@v4
diff --git a/README.md b/README.md
index f4dd93f..421da7e 100644
--- a/README.md
+++ b/README.md
@@ -452,14 +452,70 @@ For this flow the `user` field will be ignored.
## Testing
-The tests uses twisted's testing framework trial, with the development
-enviroment managed by hatch. Running the tests and generating a coverage report
+To create virtual development env and install dependencies:
+```console
+hatch shell
+```
+
+The tests use pytest, with the development environment managed by hatch. Running the tests
can be done like this:
```console
-hatch run cov
+hatch test
```
+#### Additional optional testing arguments:
+Run the tests in parallel: `-p`
+
+Collect coverage data(automatically output as `lcov.info`): `-c`
+
+#### Running a specific test:
+Selecting a specific test to run can be as easy as providing the path to the test. All tests start from
+the base test directory, `tests`. If running all tests, this can be left out. For specific tests, see
+the [pytest usage docs](https://docs.pytest.org/en/stable/how-to/usage.html#specifying-which-tests-to-run) for more information
+
+## Code Quality
+
+Use `hatch fmt` to automatically format code, enforce style rules, and check types using:
+
+- `black` and `isort` for formatting
+- `ruff` for linting
+- `mypy` for static type checking
+
+### Check Code Without Modifying It
+
+To check code quality without modifying files:
+
+- Check formatting with `isort` and `black`:
+ ```console
+ hatch fmt --check -f
+ ```
+- Check types and linting with `mypy` and `ruff`:
+ ```console
+ hatch fmt --check -l
+ ```
+- Check all of above, formatting, linting, and typing:
+ ```console
+ hatch fmt --check
+ ```
+
+### Auto-formatting Code
+
+To automatically fix issues in the code:
+
+- Format only using `black` and `isort`:
+ ```console
+ hatch fmt -f
+ ```
+- Type checks(`mypy`) and lint, fixing autofixable `ruff` issues:
+ ```console
+ hatch fmt -l
+ ```
+- Run all tools, format, lint, type-check:
+ ```console
+ hatch fmt
+ ```
+
## Releasing
After tagging a new version, manually create a Github release based on the tag. This will publish the package on PyPI.
diff --git a/pyproject.toml b/pyproject.toml
index 2acc9ba..0d59de1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,7 +23,7 @@ classifiers = [
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [
- "jwcrypto",
+ "jwcrypto<=1.5.6",
"twisted",
]
version = "0.13.0"
@@ -41,14 +41,14 @@ dependencies = [
"mock",
"matrix-synapse",
"ruff",
+ "mypy",
+ "mypy-zope",
+ "types-PyYAML",
+ "types-bleach",
+ "types-cachetools",
+ "types-requests",
+ "types-pyOpenSSL"
]
-[tool.hatch.envs.default.scripts]
-cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=synapse_token_authenticator --cov=tests {args}"
-format = "black ."
-lint = "ruff check ."
-
-[tool.hatch.envs.ci.scripts]
-cov = "pytest --cov-report=lcov:lcov.info --cov-config=pyproject.toml --cov=synapse_token_authenticator --cov=tests"
[tool.coverage.run]
branch = true
@@ -57,3 +57,108 @@ omit = []
[tool.coverage.report]
exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
+
+[tool.hatch.envs.hatch-static-analysis]
+dependencies = [
+ "black",
+ "isort",
+ "ruff",
+ "mypy",
+ "mypy-zope",
+ "types-PyYAML",
+ "types-bleach",
+ "types-cachetools",
+ "types-jwcrypto",
+ "types-requests",
+ "types-pyOpenSSL"
+]
+
+[tool.hatch.envs.hatch-static-analysis.scripts]
+format-check = [
+ "isort . --check --diff",
+ "black . --check --diff"
+]
+format-fix = [
+ "isort .",
+ "black ."
+]
+lint-check = [
+ "mypy",
+ "ruff check"
+]
+lint-fix = [
+ "mypy",
+ "ruff check --fix"
+]
+[tool.ruff]
+target-version = "py310"
+line-length = 88
+
+[tool.mypy]
+python_version = "3.10"
+ignore_missing_imports = true
+install_types = true
+plugins = [
+ "mypy_zope:plugin"
+]
+files = ["synapse_token_authenticator", "tests"]
+
+[tool.isort]
+profile = "black"
+src_paths = ["synapse_token_authenticator", "tests"]
+
+[tool.hatch.envs.hatch-test]
+# These below are installed by default. This list is to help not reinstall something.
+# Updating hatch will change the version numbers, so ignore the pinned versions.
+#dependencies = [
+# "coverage-enable-subprocess==1.0",
+# "coverage[toml]~=7.11",
+# "pytest~=9.0",
+# "pytest-mock~=3.12",
+# "pytest-randomly~=3.15",
+# "pytest-rerunfailures~=14.0",
+# "pytest-xdist[psutil]~=3.5",
+#]
+
+extra-dependencies = [
+ "pytest-cov",
+ "matrix-synapse",
+]
+extra-args = [
+ # use the loadscope dist method when running tests parallel. This keeps grouped tests
+ # together instead of splitting them up, which tends to get "stuck" and not finish
+ # the testing(have to use ^C to exit). Has no effect when tests are not parallelized.
+ "--dist=loadscope"
+]
+
+# The 'hatch test ...' command has the built-in scripts that define its invocation.
+# Override these scripts so coverage works as expected.
+#
+# In particular, the 'coverage run -m pytest ...' command does not work correctly.
+# In CI, it was not producing the subprocess-enabled coverage files, even though it
+# worked locally (there should have been 4 files, but only 1 was produced, and it had
+# no data). Using the pytest-cov plugin does seem to produce these files, and the
+# missing-lines display format is also correct. Since it handles the internal
+# combine/report commands, set those scripts below to do nothing (or CI will report
+# fake errors).
+[tool.hatch.envs.hatch-test.scripts]
+# The HATCH_TEST_ARGS environment variable is how the `test` command's flags are
+# translated and internally populated without affecting the user's arguments. This is
+# also the way that "extra arguments" are passed. Leave it there
+run = "pytest{env:HATCH_TEST_ARGS:} {args}"
+# The original 'run-cov' is below; this is overridden to produce coverage properly.
+#run-cov = "coverage run -m pytest{env:HATCH_TEST_ARGS:} {args}"
+# Notes for this command line: using --cov=synapse_token_authenticator instead of having
+# that directory in the 'coverage.run' config above actually produces the correct
+# coverage files (the ones that were missing, as mentioned above). The 'cov-report'
+# options both produce useful output (one for the codecov file and the other displays
+# the missing coverage lines).
+run-cov = "pytest{env:HATCH_TEST_ARGS:} --cov=synapse_token_authenticator --cov-report=lcov:lcov.info --cov-report=term-missing --cov-config=pyproject.toml {args}"
+
+# As mentioned above, the defaults for these scripts are commented out as they will
+# produce fake errors in CI. They attempt to run but the result of those commands
+# already exists. This raises error codes and fails the test.
+#cov-combine = "coverage combine"
+#cov-report = "coverage report"
+cov-combine = ""
+cov-report = ""
diff --git a/synapse_token_authenticator/__init__.py b/synapse_token_authenticator/__init__.py
index 4196c75..0849b00 100644
--- a/synapse_token_authenticator/__init__.py
+++ b/synapse_token_authenticator/__init__.py
@@ -1,3 +1,3 @@
-from synapse_token_authenticator.token_authenticator import (
- TokenAuthenticator, # noqa: F401
+from synapse_token_authenticator.token_authenticator import ( # noqa: F401
+ TokenAuthenticator,
)
diff --git a/synapse_token_authenticator/claims_validator.py b/synapse_token_authenticator/claims_validator.py
index 6486270..5553e11 100644
--- a/synapse_token_authenticator/claims_validator.py
+++ b/synapse_token_authenticator/claims_validator.py
@@ -16,10 +16,11 @@
more complicated, we better switch to another engine/DSL
"""
+import re
from dataclasses import dataclass
-from typing import List, Optional, Any, TypeAlias, Union
+from typing import Any, List, Optional, TypeAlias, Union
+
from synapse_token_authenticator.utils import get_path_in_dict
-import re
Validator: TypeAlias = Union[
"Exist",
diff --git a/synapse_token_authenticator/config.py b/synapse_token_authenticator/config.py
index dce1f7d..1cadbc9 100644
--- a/synapse_token_authenticator/config.py
+++ b/synapse_token_authenticator/config.py
@@ -1,13 +1,15 @@
import os
from dataclasses import dataclass, field
-from typing import List, Literal, Union, TypeAlias, Any
+from typing import Any, List, Literal, TypeAlias, Union
+
from jwcrypto.jwk import JWK, JWKSet
+
from synapse_token_authenticator.claims_validator import (
- parse_validator,
- Validator,
Exist,
+ Validator,
+ parse_validator,
)
-from synapse_token_authenticator.utils import bearer_auth, basic_auth
+from synapse_token_authenticator.utils import basic_auth, bearer_auth
class TokenAuthenticatorConfig:
diff --git a/synapse_token_authenticator/utils.py b/synapse_token_authenticator/utils.py
index ebaf7e2..30a3171 100644
--- a/synapse_token_authenticator/utils.py
+++ b/synapse_token_authenticator/utils.py
@@ -1,7 +1,8 @@
+import json
from base64 import b64encode
+from typing import Any, List, Optional
from urllib.parse import urljoin
-from typing import List, Optional, Any
-import json
+
from twisted.web import resource
diff --git a/tests/test_epa.py b/tests/test_epa.py
index 3747288..9b78531 100644
--- a/tests/test_epa.py
+++ b/tests/test_epa.py
@@ -13,13 +13,14 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from copy import deepcopy
from unittest import mock
+from jwcrypto import jwk
+
import tests.unittest as synapsetest
from . import ModuleApiTestCase, get_enc_jwk, get_jwe_token, get_jwk, get_jwt_token
-from copy import deepcopy
-from jwcrypto import jwk
def get_default_claims() -> dict:
diff --git a/tests/test_oauth.py b/tests/test_oauth.py
index cf90373..6c7eedc 100644
--- a/tests/test_oauth.py
+++ b/tests/test_oauth.py
@@ -13,13 +13,14 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from copy import deepcopy
from unittest import mock
+from jwcrypto.jwk import JWKSet
+
import tests.unittest as synapsetest
-from . import ModuleApiTestCase, get_jwt_token, get_jwk, mock_for_oauth
-from copy import deepcopy
-from jwcrypto.jwk import JWKSet
+from . import ModuleApiTestCase, get_jwk, get_jwt_token, mock_for_oauth
default_claims = {
"urn:messaging:matrix:localpart": "alice",
diff --git a/tests/test_sta_utils.py b/tests/test_sta_utils.py
index 1813f05..e518995 100644
--- a/tests/test_sta_utils.py
+++ b/tests/test_sta_utils.py
@@ -1,8 +1,8 @@
from synapse_token_authenticator.utils import (
+ all_list_elems_are_equal_return_the_elem,
get_path_in_dict,
- validate_scopes,
if_not_none,
- all_list_elems_are_equal_return_the_elem,
+ validate_scopes,
)
diff --git a/tests/test_utils/__init__.py b/tests/test_utils/__init__.py
index 6e2648d..96c3db2 100644
--- a/tests/test_utils/__init__.py
+++ b/tests/test_utils/__init__.py
@@ -15,6 +15,7 @@
"""
Utilities for running the unit tests
"""
+
import json
import sys
import warnings
diff --git a/tests/test_validators.py b/tests/test_validators.py
index d1e2cdd..fc3533d 100644
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -1,6 +1,7 @@
-from synapse_token_authenticator.claims_validator import parse_validator
from pytest import fixture
+from synapse_token_authenticator.claims_validator import parse_validator
+
def test_validator_exists():
assert parse_validator(["exist"]).validate(None)