diff --git a/mindee/cli.py b/mindee/cli.py index e096d5bd..51a29983 100644 --- a/mindee/cli.py +++ b/mindee/cli.py @@ -1,5 +1,7 @@ +import io import logging import sys +from typing import cast from mindee.v2.commands.cli_parser import MindeeParser @@ -68,6 +70,11 @@ def main() -> None: Pass ``--verbose`` (or ``-v``) to enable diagnostic logging; repeat the flag (``--verbose --verbose``) for debug-level output. """ + + stdout = cast(io.TextIOWrapper, sys.stdout) + stdout_encoding = str(stdout.encoding) + if stdout_encoding and stdout_encoding.lower() != "utf-8": + stdout.reconfigure(encoding="utf-8") verbose_level, argv = _extract_verbose_level(sys.argv[1:]) _configure_logging(verbose_level) sys.argv = [sys.argv[0], *argv] diff --git a/mindee/v2/parsing/failed_inference_response.py b/mindee/v2/parsing/failed_inference_response.py new file mode 100644 index 00000000..ddca5645 --- /dev/null +++ b/mindee/v2/parsing/failed_inference_response.py @@ -0,0 +1,33 @@ +from datetime import datetime + +from mindee.parsing.common.common_response import CommonResponse +from mindee.parsing.common.string_dict import StringDict +from mindee.v2.parsing import ErrorResponse + + +class FailedInferenceResponse(CommonResponse): + """Webhook payload returned when an inference fails before producing a result.""" + + inference_id: str + """UUID of the failed inference.""" + model_id: str + """UUID of the model used.""" + file_name: str + """Name of the input file.""" + file_alias: str + """Alias sent for the file, if any.""" + error: ErrorResponse + """Problem details for the failure, if available.""" + created_at: datetime + """Date and time when the inference was started.""" + + def __init__(self, raw_prediction: StringDict) -> None: + super().__init__(raw_prediction) + self.inference_id = raw_prediction["inference_id"] + self.model_id = raw_prediction["model_id"] + self.file_name = raw_prediction["file_name"] + self.file_alias = raw_prediction["file_alias"] + self.error = ErrorResponse(raw_prediction["error"]) + self.created_at = datetime.fromisoformat( + raw_prediction["created_at"].replace("Z", "+00:00") + ) diff --git a/tests/v1/input/test_url_validation.py b/tests/v1/input/test_url_validation.py index fa2c8f03..ac87eb72 100644 --- a/tests/v1/input/test_url_validation.py +++ b/tests/v1/input/test_url_validation.py @@ -4,7 +4,6 @@ from mindee.mindee_http.response_validation import validate_url_for_source -@pytest.mark.v1 class TestValidateUrlScheme: def test_rejects_http(self): with pytest.raises(MindeeSourceError, match="HTTPS"): @@ -18,7 +17,6 @@ def test_accepts_https(self): validate_url_for_source("https://example.com/file.pdf") -@pytest.mark.v1 class TestValidateUrlUserinfo: def test_rejects_username_and_password(self): with pytest.raises(MindeeSourceError, match="credentials"): @@ -29,7 +27,6 @@ def test_rejects_username_only(self): validate_url_for_source("https://user@example.com/file.pdf") -@pytest.mark.v1 class TestValidateUrlLoopbackHostnames: def test_rejects_localhost(self): with pytest.raises(MindeeSourceError, match="Loopback"): @@ -48,7 +45,6 @@ def test_rejects_ip6_loopback(self): validate_url_for_source("https://ip6-loopback/file.pdf") -@pytest.mark.v1 class TestValidateUrlLoopbackIPs: def test_rejects_ipv4_loopback(self): with pytest.raises(MindeeSourceError, match="disallowed"): @@ -63,7 +59,6 @@ def test_rejects_ipv6_loopback(self): validate_url_for_source("https://[::1]/file.pdf") -@pytest.mark.v1 class TestValidateUrlPrivateIPs: def test_rejects_rfc1918_10_block(self): with pytest.raises(MindeeSourceError, match="disallowed"): @@ -90,7 +85,6 @@ def test_rejects_multicast(self): validate_url_for_source("https://224.0.0.1/file.pdf") -@pytest.mark.v1 class TestValidateUrlCgnat: def test_rejects_cgnat_start(self): with pytest.raises(MindeeSourceError, match="disallowed"): @@ -105,7 +99,6 @@ def test_accepts_just_outside_cgnat(self): validate_url_for_source("https://100.128.0.1/file.pdf") -@pytest.mark.v1 class TestValidateUrlIpv6UniqueLocal: def test_rejects_ipv6_ula_fc(self): with pytest.raises(MindeeSourceError, match="disallowed"): diff --git a/tests/v2/parsing/test_failed_inference_response.py b/tests/v2/parsing/test_failed_inference_response.py new file mode 100644 index 00000000..611f721a --- /dev/null +++ b/tests/v2/parsing/test_failed_inference_response.py @@ -0,0 +1,26 @@ +import json +from datetime import datetime + +import pytest + +from mindee.v2.parsing.failed_inference_response import FailedInferenceResponse +from tests.utils import V2_DATA_DIR + + +@pytest.mark.v2 +def test_should_load_when_failed(): + """Should load when the webhook didn't return a correct reply.""" + + json_path = V2_DATA_DIR / "errors" / "webhook_error_500_failed.json" + with json_path.open("r", encoding="utf-8") as fh: + json_sample = json.load(fh) + response = FailedInferenceResponse(json_sample) + + assert response is not None + assert response.inference_id == "12345678-1234-1234-1234-123456789ABC" + assert response.file_name == "default_sample.jpg" + assert response.file_alias == "dummy-alias.jpg" + assert isinstance(response.created_at, datetime) + assert response.error is not None + assert response.error.status == 500 + assert response.error.code == "500-012"