File size: 2,653 Bytes
4107841
 
 
 
 
 
 
 
6494c95
 
4107841
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1da1792
 
 
 
 
 
 
 
 
 
 
 
 
4107841
 
 
 
6494c95
4107841
 
d8ebb7a
 
 
 
 
4107841
 
6494c95
4107841
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c566758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4107841
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
"""Typed errors. Each maps to a locale key; raw details never reach visitors (spec section 15)."""

from __future__ import annotations


class AppError(Exception):
    code = "unexpected"
    message_key = "error.generic"
    # True only when a failure after the provider call is known not to have been billed.
    unbilled = False


class EmptyInputError(AppError):
    code = "empty_input"
    message_key = "error.empty"


class TooLongInputError(AppError):
    code = "too_long"
    message_key = "error.too_long"


class ProviderTimeoutError(AppError):
    code = "timeout"
    message_key = "error.timeout"


class ProviderError(AppError):
    """A provider call failed or returned something unusable. Details stay in server logs."""

    code = "provider_error"
    message_key = "error.generic"


class MalformedReplyError(ProviderError):
    """The provider answered, but not with usable JSON. Worth one retry, unlike a refusal."""

    code = "malformed_reply"


class RateLimitedError(AppError):
    code = "rate_limited"
    message_key = "error.busy"
    unbilled = True


class CooldownError(AppError):
    code = "cooldown"
    message_key = "error.cooldown"


class BudgetReachedError(AppError):
    code = "budget_reached"
    unbilled = True
    message_key = "error.budget"


class PausedError(AppError):
    code = "paused"
    message_key = "error.paused"


class SafetyRefusalError(AppError):
    message_key = "error.safety"

    def __init__(self, code: str = "refused", rewrite: str | None = None) -> None:
        super().__init__(code)
        self.code = code
        self.rewrite = rewrite


class CheckFailedError(AppError):
    """A safety check could not run. Generation stops (fail closed)."""

    code = "check_failed"
    message_key = "error.generic"


class BadImageError(AppError):
    """An uploaded photo is not an image we can use (not an image, too big, unreadable)."""

    code = "bad_image"
    message_key = "photo.bad_image"
    unbilled = True


class ConsentRequiredError(AppError):
    """The visitor did not confirm that the photo is sent to an external service."""

    code = "consent_required"
    message_key = "photo.need_consent"
    unbilled = True


class FeatureOffError(AppError):
    """The photo studio is switched off (it is switched on from the admin account)."""

    code = "feature_off"
    message_key = "photo.off"
    unbilled = True


def error_code(exc: BaseException) -> str:
    return exc.code if isinstance(exc, AppError) else AppError.code


def error_message_key(exc: BaseException) -> str:
    return exc.message_key if isinstance(exc, AppError) else AppError.message_key