Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ Changelog

All notable changes to this project will be documented in this file.

[0.6.3] - 2025-09-24
--------------------

Added
^^^^^
- Added default timeout of 30s for external api requests.

Changed
^^^^^^^
- Switched to more strict exception types for image filter.


[0.6.2] - 2025-07-03
--------------------

Expand Down
2 changes: 1 addition & 1 deletion flask_inputfilter/filters/to_base64_image_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def apply(self, value: Any) -> Any:
try:
Image.open(io.BytesIO(base64.b64decode(value))).verify()
return value
except Exception:
except (ValueError, OSError, base64.binascii.Error):
Copy link

Copilot AI Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception base64.binascii.Error should be binascii.Error. The base64 module doesn't have a binascii attribute - it should reference the binascii module directly.

Copilot uses AI. Check for mistakes.
pass

# Try to open as raw bytes
Expand Down
2 changes: 1 addition & 1 deletion flask_inputfilter/filters/to_image_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def apply(self, value: Any) -> Any:
# Try to decode as base64
try:
return Image.open(io.BytesIO(base64.b64decode(value)))
except Exception:
except (ValueError, OSError, base64.binascii.Error):
Copy link

Copilot AI Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception base64.binascii.Error should be binascii.Error. The base64 module doesn't have a binascii attribute - it should reference the binascii module directly.

Copilot uses AI. Check for mistakes.
pass

# Try to open as raw bytes
Expand Down
4 changes: 2 additions & 2 deletions flask_inputfilter/filters/to_typed_dict_filter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import Any
from typing import Any, Type

from flask_inputfilter.models import BaseFilter

Expand Down Expand Up @@ -34,7 +34,7 @@ def __init__(self):

__slots__ = ("typed_dict",)

def __init__(self, typed_dict) -> None:
def __init__(self, typed_dict: Type) -> None:
"""
Parameters:
typed_dict (Type[TypedDict]): The TypedDict class
Expand Down
2 changes: 1 addition & 1 deletion flask_inputfilter/input_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def decorator(
"""

def wrapper(
*args, **kwargs
*args: Any, **kwargs: Any
) -> Union[Response, tuple[Any, dict[str, Any]]]:
"""
Wrapper function to handle input validation and error handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ cdef class ExternalApiMixin:
requestData["method"] = config.method

try:
response = requests.request(**requestData)
response = requests.request(timeout=30, **requestData)
result = response.json()
except requests.exceptions.RequestException:
if fallback is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def call_external_api(
request_data["method"] = config.method

try:
response = requests.request(**request_data)
response = requests.request(timeout=30, **request_data)
result = response.json()
except requests.exceptions.RequestException:
if fallback is None:
Expand Down
2 changes: 1 addition & 1 deletion flask_inputfilter/validators/is_dataclass_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def __init__(
)
)

def _format_error(self, error_type: str, **kwargs) -> str:
def _format_error(self, error_type: str, **kwargs: Any) -> str:
"""Format error message using template or custom message."""
if self.error_message:
return self.error_message
Expand Down
5 changes: 3 additions & 2 deletions flask_inputfilter/validators/is_horizontal_image_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import base64
import binascii
import io
from typing import Any, Optional

from PIL import Image
from PIL.Image import Image as ImageType
Expand Down Expand Up @@ -41,10 +42,10 @@ def __init__(self):

__slots__ = ("error_message",)

def __init__(self, error_message=None):
def __init__(self, error_message: Optional[str] = None) -> None:
self.error_message = error_message

def validate(self, value):
def validate(self, value: Any) -> None:
if not isinstance(value, (str, ImageType)):
raise ValidationError(
"The value is not an image or its base 64 representation."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __init__(self):

__slots__ = ("error_message",)

def __init__(self, error_message: Optional[str] = None):
def __init__(self, error_message: Optional[str] = None) -> None:
self.error_message = (
error_message or "The image is not vertically oriented."
)
Expand Down
36 changes: 35 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "flask_inputfilter"
version = "0.6.2"
version = "0.6.3"
description = "A library to easily filter and validate input data in Flask applications"
readme = "README.md"
keywords = [
Expand Down Expand Up @@ -125,6 +125,16 @@ select = [
"TCH", # flake8-type-checking
"PTH", # flake8-use-pathlib
"RUF", # Ruff-specific rules
"S", # flake8-bandit (Security)
"BLE", # flake8-blind-except
"ANN", # flake8-annotations
"ARG", # flake8-unused-arguments
"ERA", # eradicate
"PERF", # Perflint
"FURB", # refurb
"TRY", # tryceratops
"SLF", # flake8-self
"A", # flake8-builtins
]
fixable = ["ALL"]
unfixable = []
Expand All @@ -136,11 +146,35 @@ ignore = [
"UP006", # Use `list` instead of `List` (Python 3.9+)
"UP007", # Use `X | Y` for unions (Python 3.10+)
"UP035", # Import from collections.abc (Python 3.9+)
"ANN101", # Missing type annotation for self
"ANN102", # Missing type annotation for cls
"ANN401", # Dynamically typed expressions (Any) - OK for flexible library
"TRY003", # Long exception messages (ok for Libraries)
"TRY300", # Consider moving to else block - not always applicable
"TRY301", # Abstract raise to inner function - not needed for simple cases
"PERF203", # try-except in loop - acceptable for validation patterns
"A001", # Variable shadowing builtin - OK in specific contexts (e.g. copyright)
"A002", # Variable shadowing builtin
]
pyupgrade = [
true
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = [
"S101", # Assert usage ist OK in Tests
"ANN", # Type annotations optional in Tests
"ARG", # Unused arguments OK in Test fixtures
]
"examples/*" = [
"ANN201", # Missing return type annotation in examples
"ANN204", # Missing return type annotation for __init__ in examples
"S201", # debug=True is OK in examples
]
"docs/*" = [
"A001", # Variable shadowing builtin (copyright) is OK in docs
]

[tool.ruff.lint.isort]
force-single-line = false
split-on-trailing-comma = true
Expand Down
3 changes: 2 additions & 1 deletion tests/test_input_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ def test_external_api(self, mock_request: Mock) -> None:
self.assertTrue(validated_data["is_valid"])
expected_url = "https://api.example.com/validate_user/test_user"
mock_request.assert_called_with(
headers={}, method="GET", url=expected_url, params={}
headers={}, method="GET", url=expected_url, params={}, timeout=30
)

# API returns invalid result
Expand Down Expand Up @@ -904,6 +904,7 @@ def test_external_api_params(self, mock_request: Mock) -> None:
method="GET",
url=expected_url,
params={"hash": "1234", "id": 123},
timeout=30,
)

mock_response.status_code = 500
Expand Down