diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0e517a..41886fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.5 + rev: v0.14.6 hooks: # Run the linter. - id: ruff-check diff --git a/pyproject.toml b/pyproject.toml index 03ecb3e..7cd5081 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ lint.select = [ # pyflakes checks. "F", # flake8-bugbear checks. - "B0", + "B", # flake8-comprehensions checks. "C4", # McCabe complexity diff --git a/src/otdf_python/address_normalizer.py b/src/otdf_python/address_normalizer.py index 1b636b7..e3acf15 100644 --- a/src/otdf_python/address_normalizer.py +++ b/src/otdf_python/address_normalizer.py @@ -34,8 +34,8 @@ def normalize_address(url_string: str, use_plaintext: bool) -> str: port_str = host_port_pattern.group(2) try: port = int(port_str) - except ValueError: - raise SDKException(f"Invalid port in URL [{url_string}]") + except ValueError as err: + raise SDKException(f"Invalid port in URL [{url_string}]") from err normalized_url = f"{scheme}://{host}:{port}" logger.debug(f"normalized url [{url_string}] to [{normalized_url}]") @@ -66,8 +66,8 @@ def normalize_address(url_string: str, use_plaintext: bool) -> str: _, port_str = parsed_url.netloc.split(":", 1) try: port = int(port_str) - except ValueError: - raise SDKException(f"Invalid port in URL [{url_string}]") + except ValueError as err: + raise SDKException(f"Invalid port in URL [{url_string}]") from err # If no port was found or extracted, use the default if port is None: @@ -81,4 +81,4 @@ def normalize_address(url_string: str, use_plaintext: bool) -> str: except Exception as e: if isinstance(e, SDKException): raise e - raise SDKException(f"Error normalizing URL [{url_string}]", e) + raise SDKException(f"Error normalizing URL [{url_string}]: {e}") from e diff --git a/src/otdf_python/asym_crypto.py b/src/otdf_python/asym_crypto.py index e7cffd9..b77b609 100644 --- a/src/otdf_python/asym_crypto.py +++ b/src/otdf_python/asym_crypto.py @@ -60,7 +60,7 @@ def __init__(self, private_key_pem: str | None = None, private_key_obj=None): decoded, password=None, backend=default_backend() ) except Exception as e: - raise SDKException(f"Failed to load private key: {e}") + raise SDKException(f"Failed to load private key: {e}") from e else: self.private_key = None @@ -89,7 +89,7 @@ def decrypt(self, data: bytes) -> bytes: ), ) except Exception as e: - raise SDKException(f"Error performing decryption: {e}") + raise SDKException(f"Error performing decryption: {e}") from e class AsymEncryption: @@ -141,7 +141,7 @@ def __init__(self, public_key_pem: str | None = None, public_key_obj=None): decoded, backend=default_backend() ) except Exception as e: - raise SDKException(f"Failed to load public key: {e}") + raise SDKException(f"Failed to load public key: {e}") from e else: self.public_key = None @@ -176,7 +176,7 @@ def encrypt(self, data: bytes) -> bytes: ), ) except Exception as e: - raise SDKException(f"Error performing encryption: {e}") + raise SDKException(f"Error performing encryption: {e}") from e def public_key_in_pem_format(self) -> str: """ @@ -195,4 +195,4 @@ def public_key_in_pem_format(self) -> str: ) return pem.decode() except Exception as e: - raise SDKException(f"Error exporting public key to PEM: {e}") + raise SDKException(f"Error exporting public key to PEM: {e}") from e diff --git a/src/otdf_python/autoconfigure_utils.py b/src/otdf_python/autoconfigure_utils.py index 4206b00..a1e6c26 100644 --- a/src/otdf_python/autoconfigure_utils.py +++ b/src/otdf_python/autoconfigure_utils.py @@ -42,10 +42,10 @@ def __init__(self, url: str): raise AutoConfigureException("invalid type: attribute regex fail") try: urllib.parse.unquote(matcher.group(2)) - except Exception: + except Exception as err: raise AutoConfigureException( f"invalid type: error in attribute name [{matcher.group(2)}]" - ) + ) from err self.url = url self.key = url.lower() @@ -76,8 +76,8 @@ def name(self): raise AutoConfigureException("invalid attribute") try: return urllib.parse.unquote(matcher.group(1)) - except Exception: - raise AutoConfigureException("invalid type") + except Exception as err: + raise AutoConfigureException("invalid type") from err class AttributeValueFQN: @@ -96,8 +96,10 @@ def __init__(self, url: str): try: urllib.parse.unquote(matcher.group(2)) urllib.parse.unquote(matcher.group(3)) - except Exception: - raise AutoConfigureException("invalid type: error in attribute or value") + except Exception as err: + raise AutoConfigureException( + "invalid type: error in attribute or value" + ) from err self.url = url self.key = url.lower() diff --git a/src/otdf_python/cli.py b/src/otdf_python/cli.py index 529fb83..3c33e3c 100644 --- a/src/otdf_python/cli.py +++ b/src/otdf_python/cli.py @@ -105,11 +105,11 @@ def load_client_credentials(creds_file_path: str) -> tuple[str, str]: except json.JSONDecodeError as e: raise CLIError( "CRITICAL", f"Invalid JSON in credentials file {creds_file_path}: {e}" - ) + ) from e except Exception as e: raise CLIError( "CRITICAL", f"Error reading credentials file {creds_file_path}: {e}" - ) + ) from e def build_sdk(args) -> SDK: diff --git a/src/otdf_python/ecdh.py b/src/otdf_python/ecdh.py index c766d6e..63da6ed 100644 --- a/src/otdf_python/ecdh.py +++ b/src/otdf_python/ecdh.py @@ -156,7 +156,7 @@ def decompress_public_key( return ec.EllipticCurvePublicKey.from_encoded_point(curve, compressed_key) except (ValueError, TypeError) as e: - raise InvalidKeyError(f"Failed to decompress public key: {e}") + raise InvalidKeyError(f"Failed to decompress public key: {e}") from e def derive_shared_secret( @@ -179,7 +179,7 @@ def derive_shared_secret( shared_secret = private_key.exchange(ec.ECDH(), public_key) return shared_secret except Exception as e: - raise ECDHError(f"Failed to derive shared secret: {e}") + raise ECDHError(f"Failed to derive shared secret: {e}") from e def derive_key_from_shared_secret( @@ -216,7 +216,7 @@ def derive_key_from_shared_secret( ) return hkdf.derive(shared_secret) except Exception as e: - raise ECDHError(f"Failed to derive key from shared secret: {e}") + raise ECDHError(f"Failed to derive key from shared secret: {e}") from e def encrypt_key_with_ecdh( @@ -251,7 +251,7 @@ def encrypt_key_with_ecdh( if not isinstance(recipient_public_key, ec.EllipticCurvePublicKey): raise InvalidKeyError("Recipient's public key is not an EC key") except Exception as e: - raise InvalidKeyError(f"Failed to load recipient's public key: {e}") + raise InvalidKeyError(f"Failed to load recipient's public key: {e}") from e # Generate ephemeral keypair ephemeral_private_key, ephemeral_public_key = generate_ephemeral_keypair(curve_name) @@ -301,7 +301,7 @@ def decrypt_key_with_ecdh( if not isinstance(recipient_private_key, ec.EllipticCurvePrivateKey): raise InvalidKeyError("Recipient's private key is not an EC key") except Exception as e: - raise InvalidKeyError(f"Failed to load recipient's private key: {e}") + raise InvalidKeyError(f"Failed to load recipient's private key: {e}") from e # Decompress ephemeral public key ephemeral_public_key = decompress_public_key( diff --git a/src/otdf_python/kas_client.py b/src/otdf_python/kas_client.py index e746741..1b4a669 100644 --- a/src/otdf_python/kas_client.py +++ b/src/otdf_python/kas_client.py @@ -78,7 +78,7 @@ def _normalize_kas_url(self, url: str) -> str: # Parse the URL parsed = urlparse(url) except Exception as e: - raise SDKException(f"error trying to parse URL [{url}]", e) + raise SDKException(f"error trying to parse URL [{url}]: {e}") from e # Check if we have a host or if this is likely a hostname:port combination if parsed.hostname is None: @@ -100,10 +100,10 @@ def _handle_missing_scheme(self, url: str) -> str: try: port = int(port_str) return f"{scheme}://{host}:{port}" - except ValueError: + except ValueError as err: raise SDKException( f"error trying to create URL for host and port [{url}]" - ) + ) from err else: # Hostname with or without path, add default port if "/" in url: @@ -116,7 +116,7 @@ def _handle_missing_scheme(self, url: str) -> str: except Exception as e: raise SDKException( f"error trying to create URL for host and port [{url}]", e - ) + ) from e def _handle_existing_scheme(self, parsed) -> str: """Handle URLs with existing scheme by normalizing protocol and port.""" @@ -138,7 +138,7 @@ def _handle_existing_scheme(self, parsed) -> str: logging.debug(f"normalized url [{parsed.geturl()}] to [{normalized_url}]") return normalized_url except Exception as e: - raise SDKException("error creating KAS address", e) + raise SDKException(f"error creating KAS address: {e}") from e def _get_wrapped_key_base64(self, key_access): """ @@ -483,7 +483,7 @@ def _get_public_key_with_connect_rpc(self, kas_info): f"Connect RPC public key request failed: {type(e).__name__}: {e}" ) logging.error(f"Full traceback: {error_details}") - raise SDKException(f"Connect RPC public key request failed: {e}") + raise SDKException(f"Connect RPC public key request failed: {e}") from e def _normalize_session_key_type(self, session_key_type): """ @@ -608,7 +608,7 @@ def _parse_and_decrypt_response(self, response): except Exception as e: logging.error(f"Failed to parse JSON response: {e}") logging.error(f"Raw response content: {response.content}") - raise SDKException(f"Invalid JSON response from KAS: {e}") + raise SDKException(f"Invalid JSON response from KAS: {e}") from e entity_wrapped_key = response_data.get("entityWrappedKey") if not entity_wrapped_key: @@ -702,7 +702,7 @@ def _unwrap_with_connect_rpc( except Exception as e: logging.error(f"Connect RPC rewrap failed: {e}") - raise SDKException(f"Connect RPC rewrap failed: {e}") + raise SDKException(f"Connect RPC rewrap failed: {e}") from e def get_key_cache(self) -> KASKeyCache: """Returns the KAS key cache used for storing and retrieving encryption keys.""" diff --git a/src/otdf_python/kas_connect_rpc_client.py b/src/otdf_python/kas_connect_rpc_client.py index e70fe63..9589f92 100644 --- a/src/otdf_python/kas_connect_rpc_client.py +++ b/src/otdf_python/kas_connect_rpc_client.py @@ -138,7 +138,7 @@ def get_public_key(self, normalized_kas_url, kas_info, access_token=None): f"Connect RPC public key request failed: {type(e).__name__}: {e}" ) logging.error(f"Full traceback: {error_details}") - raise SDKException(f"Connect RPC public key request failed: {e}") + raise SDKException(f"Connect RPC public key request failed: {e}") from e def unwrap_key( self, normalized_kas_url, key_access, signed_token, access_token=None @@ -210,4 +210,4 @@ def unwrap_key( except Exception as e: logging.error(f"Connect RPC rewrap failed: {e}") - raise SDKException(f"Connect RPC rewrap failed: {e}") + raise SDKException(f"Connect RPC rewrap failed: {e}") from e diff --git a/src/otdf_python/nanotdf.py b/src/otdf_python/nanotdf.py index 86308ca..40d0618 100644 --- a/src/otdf_python/nanotdf.py +++ b/src/otdf_python/nanotdf.py @@ -260,7 +260,7 @@ def _is_ec_key(self, key_pem: str) -> bool: else: raise SDKException("Invalid PEM format - no BEGIN header found") except Exception as e: - raise SDKException(f"Failed to detect key type: {e}") + raise SDKException(f"Failed to detect key type: {e}") from e def _derive_key_with_ecdh( # noqa: C901 self, config: NanoTDFConfig @@ -582,7 +582,7 @@ def read_nano_tdf( # noqa: C901 header_len = Header.peek_length(nano_tdf_data) header_obj = Header.from_bytes(nano_tdf_data[:header_len]) except Exception as e: - raise InvalidNanoTDFConfig(f"Failed to parse NanoTDF header: {e}") + raise InvalidNanoTDFConfig(f"Failed to parse NanoTDF header: {e}") from e # Read payload section per NanoTDF spec: # [3 bytes: length] [3 bytes: IV] [variable: ciphertext] [tag] diff --git a/src/otdf_python/sdk_builder.py b/src/otdf_python/sdk_builder.py index 322452d..a1059db 100644 --- a/src/otdf_python/sdk_builder.py +++ b/src/otdf_python/sdk_builder.py @@ -283,7 +283,7 @@ def _discover_token_endpoint(self) -> None: pass raise AutoConfigureException( f"Error during token endpoint discovery: {e!s}" - ) + ) from e # Fall back to explicit issuer endpoint if self.issuer_endpoint: @@ -341,7 +341,9 @@ def _get_token_from_client_credentials(self) -> str: ) except Exception as e: - raise AutoConfigureException(f"Error during token acquisition: {e!s}") + raise AutoConfigureException( + f"Error during token acquisition: {e!s}" + ) from e def _create_services(self) -> SDK.Services: """ diff --git a/src/otdf_python/tdf.py b/src/otdf_python/tdf.py index a15e981..3951ecb 100644 --- a/src/otdf_python/tdf.py +++ b/src/otdf_python/tdf.py @@ -74,7 +74,7 @@ def _validate_kas_infos(self, kas_infos): except Exception as e: raise ValueError( f"Failed to fetch public key for KAS {kas.url}: {e}" - ) + ) from e else: raise ValueError( "Each KAS info must have a public_key, or SDK services must be available to fetch it" diff --git a/src/otdf_python/zip_reader.py b/src/otdf_python/zip_reader.py index 78d7ecb..73f2b07 100644 --- a/src/otdf_python/zip_reader.py +++ b/src/otdf_python/zip_reader.py @@ -17,7 +17,7 @@ def get_data(self) -> bytes: try: return self._zipfile.read(self._zipinfo) except Exception as e: - raise InvalidZipException(f"Error reading entry data: {e}") + raise InvalidZipException(f"Error reading entry data: {e}") from e def __init__(self, in_stream: io.BytesIO | bytes | None = None): try: @@ -29,7 +29,7 @@ def __init__(self, in_stream: io.BytesIO | bytes | None = None): self.Entry(self.zipfile, zi) for zi in self.zipfile.infolist() ] except zipfile.BadZipFile as e: - raise InvalidZipException(f"Invalid ZIP file: {e}") + raise InvalidZipException(f"Invalid ZIP file: {e}") from e def get_entries(self) -> list: return self.entries diff --git a/tests/integration/test_cli_tdf_validation.py b/tests/integration/test_cli_tdf_validation.py index c484cca..54a180a 100644 --- a/tests/integration/test_cli_tdf_validation.py +++ b/tests/integration/test_cli_tdf_validation.py @@ -165,7 +165,7 @@ def _validate_tdf_zip_structure(tdf_path: Path) -> None: assert "uuid" in policy_obj, "policy missing 'uuid' field" assert "body" in policy_obj, "policy missing 'body' field" except Exception as e: - raise AssertionError(f"Failed to decode base64 policy: {e}") + raise AssertionError(f"Failed to decode base64 policy: {e}") from e # Validate method structure method = enc_info["method"] @@ -216,9 +216,9 @@ def _validate_tdf_zip_structure(tdf_path: Path) -> None: ) except json.JSONDecodeError as e: - raise AssertionError(f"Manifest is not valid JSON: {e}") + raise AssertionError(f"Manifest is not valid JSON: {e}") from e except KeyError as e: - raise AssertionError(f"Manifest missing required field: {e}") + raise AssertionError(f"Manifest missing required field: {e}") from e # Check for payload file (usually 0.payload) payload_files = [f for f in file_list if f.endswith(".payload")] diff --git a/tests/integration/test_pe_interaction.py b/tests/integration/test_pe_interaction.py index 3758811..80ebaee 100644 --- a/tests/integration/test_pe_interaction.py +++ b/tests/integration/test_pe_interaction.py @@ -35,7 +35,7 @@ def decrypt(input_path: Path, output_path: Path, sdk: SDK): logger.error(f"Decryption failed: {e}") # Clean up the output file if there was an error output_path.unlink(missing_ok=True) - raise SDKException("Decryption failed") + raise SDKException("Decryption failed") from e @pytest.mark.integration diff --git a/tests/support_otdfctl.py b/tests/support_otdfctl.py index 1ec0a2e..80f411f 100644 --- a/tests/support_otdfctl.py +++ b/tests/support_otdfctl.py @@ -20,7 +20,7 @@ def check_for_otdfctl(): capture_output=True, check=True, ) - except (subprocess.CalledProcessError, FileNotFoundError): + except (subprocess.CalledProcessError, FileNotFoundError) as err: raise Exception( "otdfctl command not found on system. Please install otdfctl to run this test." - ) + ) from err