Plain is headed towards 1.0! Subscribe for development updates →

Plain

  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    silent_variable_failure = True
 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 RequestAborted(Exception):
 90    """The request was closed before it was completed, or timed out."""
 91
 92    pass
 93
 94
 95class BadRequest(Exception):
 96    """The request is malformed and cannot be processed."""
 97
 98    pass
 99
100
101class PermissionDenied(Exception):
102    """The user did not have permission to do that"""
103
104    pass
105
106
107class ViewDoesNotExist(Exception):
108    """The requested view does not exist"""
109
110    pass
111
112
113class ImproperlyConfigured(Exception):
114    """Plain is somehow improperly configured"""
115
116    pass
117
118
119class FieldError(Exception):
120    """Some kind of problem with a model field."""
121
122    pass
123
124
125NON_FIELD_ERRORS = "__all__"
126
127
128class ValidationError(Exception):
129    """An error while validating data."""
130
131    def __init__(self, message, code=None, params=None):
132        """
133        The `message` argument can be a single error, a list of errors, or a
134        dictionary that maps field names to lists of errors. What we define as
135        an "error" can be either a simple string or an instance of
136        ValidationError with its message attribute set, and what we define as
137        list or dictionary can be an actual `list` or `dict` or an instance
138        of ValidationError with its `error_list` or `error_dict` attribute set.
139        """
140        super().__init__(message, code, params)
141
142        if isinstance(message, ValidationError):
143            if hasattr(message, "error_dict"):
144                message = message.error_dict
145            elif not hasattr(message, "message"):
146                message = message.error_list
147            else:
148                message, code, params = message.message, message.code, message.params
149
150        if isinstance(message, dict):
151            self.error_dict = {}
152            for field, messages in message.items():
153                if not isinstance(messages, ValidationError):
154                    messages = ValidationError(messages)
155                self.error_dict[field] = messages.error_list
156
157        elif isinstance(message, list):
158            self.error_list = []
159            for message in message:
160                # Normalize plain strings to instances of ValidationError.
161                if not isinstance(message, ValidationError):
162                    message = ValidationError(message)
163                if hasattr(message, "error_dict"):
164                    self.error_list.extend(sum(message.error_dict.values(), []))
165                else:
166                    self.error_list.extend(message.error_list)
167
168        else:
169            self.message = message
170            self.code = code
171            self.params = params
172            self.error_list = [self]
173
174    @property
175    def message_dict(self):
176        # Trigger an AttributeError if this ValidationError
177        # doesn't have an error_dict.
178        getattr(self, "error_dict")
179
180        return dict(self)
181
182    @property
183    def messages(self):
184        if hasattr(self, "error_dict"):
185            return sum(dict(self).values(), [])
186        return list(self)
187
188    def update_error_dict(self, error_dict):
189        if hasattr(self, "error_dict"):
190            for field, error_list in self.error_dict.items():
191                error_dict.setdefault(field, []).extend(error_list)
192        else:
193            error_dict.setdefault(NON_FIELD_ERRORS, []).extend(self.error_list)
194        return error_dict
195
196    def __iter__(self):
197        if hasattr(self, "error_dict"):
198            for field, errors in self.error_dict.items():
199                yield field, list(ValidationError(errors))
200        else:
201            for error in self.error_list:
202                message = error.message
203                if error.params:
204                    message %= error.params
205                yield str(message)
206
207    def __str__(self):
208        if hasattr(self, "error_dict"):
209            return repr(dict(self))
210        return repr(list(self))
211
212    def __repr__(self):
213        return f"ValidationError({self})"
214
215    def __eq__(self, other):
216        if not isinstance(other, ValidationError):
217            return NotImplemented
218        return hash(self) == hash(other)
219
220    def __hash__(self):
221        if hasattr(self, "message"):
222            return hash(
223                (
224                    self.message,
225                    self.code,
226                    make_hashable(self.params),
227                )
228            )
229        if hasattr(self, "error_dict"):
230            return hash(make_hashable(self.error_dict))
231        return hash(tuple(sorted(self.error_list, key=operator.attrgetter("message"))))
232
233
234class EmptyResultSet(Exception):
235    """A database query predicate is impossible."""
236
237    pass
238
239
240class FullResultSet(Exception):
241    """A database query predicate is matches everything."""
242
243    pass