diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index b374a9a7..a1ef5e2d 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -25,4 +25,5 @@ jobs:
- name: Install dependencies and run linting
run: |
make install
+ make format-check
make lint
diff --git a/Makefile b/Makefile
index e764a496..d01d75a3 100644
--- a/Makefile
+++ b/Makefile
@@ -10,14 +10,16 @@ install:
# Run linting checks
lint:
- uv run black --check src/ test/
- # Enforce ruff checks on src/ but allow test/ to fail
uv run ruff check src/
uv run ruff check test/ || true
-# Format code with Black and Ruff
+# Check formatting (no changes, just verify)
+format-check:
+ uv run ruff format --check src/ test/
+
+# Format code
format:
- uv run black src/ test/
+ uv run ruff format src/ test/
uv run ruff check --fix src/ test/
# Run all tests with combined coverage
diff --git a/pyproject.toml b/pyproject.toml
index 5e94ee4d..7fb8a58e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,6 @@ test = [
"pytest-cov>=7.1.0",
]
dev = [
- "black>=26.3.1",
"ruff>=0.15.12",
"boto3-stubs~=1.43.6",
]
@@ -32,11 +31,6 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/s3_encryption"]
-[tool.black]
-line-length = 100
-target-version = ["py311"]
-include = '\.pyi?$'
-
[tool.ruff]
line-length = 100
target-version = "py311"
diff --git a/src/s3_encryption/instruction_file.py b/src/s3_encryption/instruction_file.py
index 61f9b167..61df766f 100644
--- a/src/s3_encryption/instruction_file.py
+++ b/src/s3_encryption/instruction_file.py
@@ -47,7 +47,7 @@ def parse_instruction_file(instruction_data: bytes, key: str) -> dict[str, Any]:
# Validate that it's a dictionary
if not isinstance(metadata, dict):
raise S3EncryptionClientError(
- f"Instruction file must contain a JSON object, " f"got {type(metadata).__name__}: {key}"
+ f"Instruction file must contain a JSON object, got {type(metadata).__name__}: {key}"
)
# Validate that all keys are S3EC metadata keys
diff --git a/src/s3_encryption/materials/kms_keyring.py b/src/s3_encryption/materials/kms_keyring.py
index abd6fad4..8eb8b7d2 100644
--- a/src/s3_encryption/materials/kms_keyring.py
+++ b/src/s3_encryption/materials/kms_keyring.py
@@ -164,8 +164,7 @@ def on_decrypt(self, dec_materials, encrypted_data_keys=None):
##% context.
if KMS_CONTEXT_DEFAULT_KEY in encryption_context_from_request:
raise S3EncryptionClientError(
- f"{KMS_CONTEXT_DEFAULT_KEY} is a reserved key for the "
- f"S3 encryption client"
+ f"{KMS_CONTEXT_DEFAULT_KEY} is a reserved key for the S3 encryption client"
)
##= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context
@@ -183,8 +182,7 @@ def on_decrypt(self, dec_materials, encrypted_data_keys=None):
if encryption_context_stored_copy != encryption_context_from_request:
# TODO: modeled error
raise S3EncryptionClientError(
- "Provided encryption context does not match information "
- "retrieved from S3"
+ "Provided encryption context does not match information retrieved from S3"
)
##= specification/s3-encryption/materials/s3-kms-keyring.md#decryptdatakey
diff --git a/src/s3_encryption/pipelines.py b/src/s3_encryption/pipelines.py
index 2b1fe061..23a169ae 100644
--- a/src/s3_encryption/pipelines.py
+++ b/src/s3_encryption/pipelines.py
@@ -260,7 +260,6 @@ def decrypt(
# Check if we need to fetch instruction file
if metadata.should_use_instruction_file():
-
if self.instruction_file_config.disable_get_object:
raise S3EncryptionClientError(
"Exception encountered while fetching Instruction File. "
@@ -371,9 +370,7 @@ def decrypt(
##% [legacy unauthenticated algorithm suites](#legacy-decryption) is NOT enabled,
##% the S3EC MUST throw an error which details that client was
##% not configured to decrypt objects with ALG_AES_256_CBC_IV16_NO_KDF.
- if (
- algorithm_suite.is_legacy and not self.enable_legacy_unauthenticated_modes
- ): # noqa: SIM102
+ if algorithm_suite.is_legacy and not self.enable_legacy_unauthenticated_modes: # noqa: SIM102
##= specification/s3-encryption/decryption.md#legacy-decryption
##= type=implementation
##% The S3EC MUST NOT decrypt objects encrypted using legacy unauthenticated algorithm suites
diff --git a/test/integration/test_i_security.py b/test/integration/test_i_security.py
index 67782c67..3d871e1f 100644
--- a/test/integration/test_i_security.py
+++ b/test/integration/test_i_security.py
@@ -81,9 +81,9 @@ def test_v3_downgrade_wrap_alg_to_kms_rejected_without_legacy(self):
plain_s3 = boto3.client("s3")
head = plain_s3.head_object(Bucket=bucket, Key=key)
original_metadata = head["Metadata"]
- assert (
- original_metadata.get("x-amz-w") == "12"
- ), f"Expected x-amz-w='12', got {original_metadata.get('x-amz-w')}"
+ assert original_metadata.get("x-amz-w") == "12", (
+ f"Expected x-amz-w='12', got {original_metadata.get('x-amz-w')}"
+ )
tampered_metadata = original_metadata.copy()
tampered_metadata["x-amz-w"] = "kms"
@@ -348,9 +348,9 @@ def test_v2_downgrade_wrap_alg_to_kms_mismatched_context(self):
plain_s3 = boto3.client("s3")
head = plain_s3.head_object(Bucket=bucket, Key=key)
original_metadata = head["Metadata"]
- assert (
- original_metadata.get("x-amz-wrap-alg") == "kms+context"
- ), f"Expected x-amz-wrap-alg='kms+context', got {original_metadata.get('x-amz-wrap-alg')}"
+ assert original_metadata.get("x-amz-wrap-alg") == "kms+context", (
+ f"Expected x-amz-wrap-alg='kms+context', got {original_metadata.get('x-amz-wrap-alg')}"
+ )
tampered_metadata = original_metadata.copy()
tampered_metadata["x-amz-wrap-alg"] = "kms"
@@ -476,14 +476,13 @@ def test_wrong_key_and_tampered_ciphertext_produce_same_error(self):
# Both MUST produce the same error message
assert str(exc1.value) == str(exc2.value), (
- f"Error messages differ: wrong_key={str(exc1.value)!r}, "
- f"tampered={str(exc2.value)!r}"
+ f"Error messages differ: wrong_key={str(exc1.value)!r}, tampered={str(exc2.value)!r}"
)
# Neither message should contain details about the underlying failure
- assert (
- "padding" not in str(exc1.value).lower()
- ), f"Error message leaks padding information: {str(exc1.value)!r}"
+ assert "padding" not in str(exc1.value).lower(), (
+ f"Error message leaks padding information: {str(exc1.value)!r}"
+ )
def test_truncated_ciphertext_produces_same_error(self):
"""Truncated ciphertext MUST produce the same error as padding failure.
diff --git a/test/performance/generate_report.py b/test/performance/generate_report.py
index 14030657..aee53864 100644
--- a/test/performance/generate_report.py
+++ b/test/performance/generate_report.py
@@ -76,7 +76,7 @@ def _bar_chart_svg(chart_id, title, groups, sizes, width=700, bar_h=28, gap=6):
lx = label_col_w
for g in groups:
svg.append(f'