Plain is headed towards 1.0! Subscribe for development updates →

  1"""
  2Global Plain exception and warning classes.
  3"""
  4
  5import operator
  6
  7from plain.utils.hashable import make_hashable
  8
  9
 10class FieldDoesNotExist(Exception):
 11    """The requested model field does not exist"""
 12
 13    pass
 14
 15
 16class PackageRegistryNotReady(Exception):
 17    """The plain.packages registry is not populated yet"""
 18
 19    pass
 20
 21
 22class ObjectDoesNotExist(Exception):
 23    """The requested object does not exist"""
 24
 25    pass
 26
 27
 28class MultipleObjectsReturned(Exception):
 29    """The query returned multiple objects when only one was expected."""
 30
 31    pass
 32
 33
 34class SuspiciousOperation(Exception):
 35    """The user did something suspicious"""
 36
 37
 38class SuspiciousMultipartForm(SuspiciousOperation):
 39    """Suspect MIME request in multipart form data"""
 40
 41    pass
 42
 43
 44class SuspiciousFileOperation(SuspiciousOperation):
 45    """A Suspicious filesystem operation was attempted"""
 46
 47    pass
 48
 49
 50class DisallowedHost(SuspiciousOperation):
 51    """HTTP_HOST header contains invalid value"""
 52
 53    pass
 54
 55
 56class DisallowedRedirect(SuspiciousOperation):
 57    """Redirect to scheme not in allowed list"""
 58
 59    pass
 60
 61
 62class TooManyFieldsSent(SuspiciousOperation):
 63    """
 64    The number of fields in a GET or POST request exceeded
 65    settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.
 66    """
 67
 68    pass
 69
 70
 71class TooManyFilesSent(SuspiciousOperation):
 72    """
 73    The number of fields in a GET or POST request exceeded
 74    settings.DATA_UPLOAD_MAX_NUMBER_FILES.
 75    """
 76
 77    pass
 78
 79
 80class RequestDataTooBig(SuspiciousOperation):
 81    """
 82    The size of the request (excluding any file uploads) exceeded
 83    settings.DATA_UPLOAD_MAX_MEMORY_SIZE.
 84    """
 85
 86    pass
 87
 88
 89class BadRequest(Exception):
 90    """The request is malformed and cannot be processed."""
 91
 92    pass
 93
 94
 95class PermissionDenied(Exception):
 96    """The user did not have permission to do that"""
 97
 98    pass
 99
100
101class ImproperlyConfigured(Exception):
102    """Plain is somehow improperly configured"""
103
104    pass
105
106
107class FieldError(Exception):
108    """Some kind of problem with a model field."""
109
110    pass
111
112
113NON_FIELD_ERRORS = "__all__"
114
115
116class ValidationError(Exception):
117    """An error while validating data."""
118
119    def __init__(self, message, code=None, params=None):
120        """
121        The `message` argument can be a single error, a list of errors, or a
122        dictionary that maps field names to lists of errors. What we define as
123        an "error" can be either a simple string or an instance of
124        ValidationError with its message attribute set, and what we define as
125        list or dictionary can be an actual `list` or `dict` or an instance
126        of ValidationError with its `error_list` or `error_dict` attribute set.
127        """
128        super().__init__(message, code, params)
129
130        if isinstance(message, ValidationError):
131            if hasattr(message, "error_dict"):
132                message = message.error_dict
133            elif not hasattr(message, "message"):
134                message = message.error_list
135            else:
136                message, code, params = message.message, message.code, message.params
137
138        if isinstance(message, dict):
139            self.error_dict = {}
140            for field, messages in message.items():
141                if not isinstance(messages, ValidationError):
142                    messages = ValidationError(messages)
143                self.error_dict[field] = messages.error_list
144
145        elif isinstance(message, list):
146            self.error_list = []
147            for message in message:
148                # Normalize plain strings to instances of ValidationError.
149                if not isinstance(message, ValidationError):
150                    message = ValidationError(message)
151                if hasattr(message, "error_dict"):
152                    self.error_list.extend(sum(message.error_dict.values(), []))
153                else:
154                    self.error_list.extend(message.error_list)
155
156        else:
157            self.message = message
158            self.code = code
159            self.params = params
160            self.error_list = [self]
161
162    @property
163    def messages(self):
164        if hasattr(self, "error_dict"):
165            return sum(dict(self).values(), [])
166        return list(self)
167
168    def update_error_dict(self, error_dict):
169        if hasattr(self, "error_dict"):
170            for field, error_list in self.error_dict.items():
171                error_dict.setdefault(field, []).extend(error_list)
172        else:
173            error_dict.setdefault(NON_FIELD_ERRORS, []).extend(self.error_list)
174        return error_dict
175
176    def __iter__(self):
177        if hasattr(self, "error_dict"):
178            for field, errors in self.error_dict.items():
179                yield field, list(ValidationError(errors))
180        else:
181            for error in self.error_list:
182                message = error.message
183                if error.params:
184                    message %= error.params
185                yield str(message)
186
187    def __str__(self):
188        if hasattr(self, "error_dict"):
189            return repr(dict(self))
190        return repr(list(self))
191
192    def __repr__(self):
193        return f"ValidationError({self})"
194
195    def __eq__(self, other):
196        if not isinstance(other, ValidationError):
197            return NotImplemented
198        return hash(self) == hash(other)
199
200    def __hash__(self):
201        if hasattr(self, "message"):
202            return hash(
203                (
204                    self.message,
205                    self.code,
206                    make_hashable(self.params),
207                )
208            )
209        if hasattr(self, "error_dict"):
210            return hash(make_hashable(self.error_dict))
211        return hash(tuple(sorted(self.error_list, key=operator.attrgetter("message"))))
212
213
214class EmptyResultSet(Exception):
215    """A database query predicate is impossible."""
216
217    pass
218
219
220class FullResultSet(Exception):
221    """A database query predicate is matches everything."""
222
223    pass