plain 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. plain/README.md +33 -0
  2. plain/__main__.py +5 -0
  3. plain/assets/README.md +56 -0
  4. plain/assets/__init__.py +6 -0
  5. plain/assets/finders.py +233 -0
  6. plain/assets/preflight.py +14 -0
  7. plain/assets/storage.py +916 -0
  8. plain/assets/utils.py +52 -0
  9. plain/assets/whitenoise/__init__.py +5 -0
  10. plain/assets/whitenoise/base.py +259 -0
  11. plain/assets/whitenoise/compress.py +189 -0
  12. plain/assets/whitenoise/media_types.py +137 -0
  13. plain/assets/whitenoise/middleware.py +197 -0
  14. plain/assets/whitenoise/responders.py +286 -0
  15. plain/assets/whitenoise/storage.py +178 -0
  16. plain/assets/whitenoise/string_utils.py +13 -0
  17. plain/cli/README.md +123 -0
  18. plain/cli/__init__.py +3 -0
  19. plain/cli/cli.py +439 -0
  20. plain/cli/formatting.py +61 -0
  21. plain/cli/packages.py +73 -0
  22. plain/cli/print.py +9 -0
  23. plain/cli/startup.py +33 -0
  24. plain/csrf/README.md +3 -0
  25. plain/csrf/middleware.py +466 -0
  26. plain/csrf/views.py +10 -0
  27. plain/debug.py +23 -0
  28. plain/exceptions.py +242 -0
  29. plain/forms/README.md +14 -0
  30. plain/forms/__init__.py +8 -0
  31. plain/forms/boundfield.py +58 -0
  32. plain/forms/exceptions.py +11 -0
  33. plain/forms/fields.py +1030 -0
  34. plain/forms/forms.py +297 -0
  35. plain/http/README.md +1 -0
  36. plain/http/__init__.py +51 -0
  37. plain/http/cookie.py +20 -0
  38. plain/http/multipartparser.py +743 -0
  39. plain/http/request.py +754 -0
  40. plain/http/response.py +719 -0
  41. plain/internal/__init__.py +0 -0
  42. plain/internal/files/README.md +3 -0
  43. plain/internal/files/__init__.py +3 -0
  44. plain/internal/files/base.py +161 -0
  45. plain/internal/files/locks.py +127 -0
  46. plain/internal/files/move.py +102 -0
  47. plain/internal/files/temp.py +79 -0
  48. plain/internal/files/uploadedfile.py +150 -0
  49. plain/internal/files/uploadhandler.py +254 -0
  50. plain/internal/files/utils.py +78 -0
  51. plain/internal/handlers/__init__.py +0 -0
  52. plain/internal/handlers/base.py +133 -0
  53. plain/internal/handlers/exception.py +145 -0
  54. plain/internal/handlers/wsgi.py +216 -0
  55. plain/internal/legacy/__init__.py +0 -0
  56. plain/internal/legacy/__main__.py +12 -0
  57. plain/internal/legacy/management/__init__.py +414 -0
  58. plain/internal/legacy/management/base.py +692 -0
  59. plain/internal/legacy/management/color.py +113 -0
  60. plain/internal/legacy/management/commands/__init__.py +0 -0
  61. plain/internal/legacy/management/commands/collectstatic.py +297 -0
  62. plain/internal/legacy/management/sql.py +67 -0
  63. plain/internal/legacy/management/utils.py +175 -0
  64. plain/json.py +40 -0
  65. plain/logs/README.md +24 -0
  66. plain/logs/__init__.py +5 -0
  67. plain/logs/configure.py +39 -0
  68. plain/logs/loggers.py +74 -0
  69. plain/logs/utils.py +46 -0
  70. plain/middleware/README.md +3 -0
  71. plain/middleware/__init__.py +0 -0
  72. plain/middleware/clickjacking.py +52 -0
  73. plain/middleware/common.py +87 -0
  74. plain/middleware/gzip.py +64 -0
  75. plain/middleware/security.py +64 -0
  76. plain/packages/README.md +41 -0
  77. plain/packages/__init__.py +4 -0
  78. plain/packages/config.py +259 -0
  79. plain/packages/registry.py +438 -0
  80. plain/paginator.py +187 -0
  81. plain/preflight/README.md +3 -0
  82. plain/preflight/__init__.py +38 -0
  83. plain/preflight/compatibility/__init__.py +0 -0
  84. plain/preflight/compatibility/django_4_0.py +20 -0
  85. plain/preflight/files.py +19 -0
  86. plain/preflight/messages.py +88 -0
  87. plain/preflight/registry.py +72 -0
  88. plain/preflight/security/__init__.py +0 -0
  89. plain/preflight/security/base.py +268 -0
  90. plain/preflight/security/csrf.py +40 -0
  91. plain/preflight/urls.py +117 -0
  92. plain/runtime/README.md +75 -0
  93. plain/runtime/__init__.py +61 -0
  94. plain/runtime/global_settings.py +199 -0
  95. plain/runtime/user_settings.py +353 -0
  96. plain/signals/README.md +14 -0
  97. plain/signals/__init__.py +5 -0
  98. plain/signals/dispatch/__init__.py +9 -0
  99. plain/signals/dispatch/dispatcher.py +320 -0
  100. plain/signals/dispatch/license.txt +35 -0
  101. plain/signing.py +299 -0
  102. plain/templates/README.md +20 -0
  103. plain/templates/__init__.py +6 -0
  104. plain/templates/core.py +24 -0
  105. plain/templates/jinja/README.md +227 -0
  106. plain/templates/jinja/__init__.py +22 -0
  107. plain/templates/jinja/defaults.py +119 -0
  108. plain/templates/jinja/extensions.py +39 -0
  109. plain/templates/jinja/filters.py +28 -0
  110. plain/templates/jinja/globals.py +19 -0
  111. plain/test/README.md +3 -0
  112. plain/test/__init__.py +16 -0
  113. plain/test/client.py +985 -0
  114. plain/test/utils.py +255 -0
  115. plain/urls/README.md +3 -0
  116. plain/urls/__init__.py +40 -0
  117. plain/urls/base.py +118 -0
  118. plain/urls/conf.py +94 -0
  119. plain/urls/converters.py +66 -0
  120. plain/urls/exceptions.py +9 -0
  121. plain/urls/resolvers.py +731 -0
  122. plain/utils/README.md +3 -0
  123. plain/utils/__init__.py +0 -0
  124. plain/utils/_os.py +52 -0
  125. plain/utils/cache.py +327 -0
  126. plain/utils/connection.py +84 -0
  127. plain/utils/crypto.py +76 -0
  128. plain/utils/datastructures.py +345 -0
  129. plain/utils/dateformat.py +329 -0
  130. plain/utils/dateparse.py +154 -0
  131. plain/utils/dates.py +76 -0
  132. plain/utils/deconstruct.py +54 -0
  133. plain/utils/decorators.py +90 -0
  134. plain/utils/deprecation.py +6 -0
  135. plain/utils/duration.py +44 -0
  136. plain/utils/email.py +12 -0
  137. plain/utils/encoding.py +235 -0
  138. plain/utils/functional.py +456 -0
  139. plain/utils/hashable.py +26 -0
  140. plain/utils/html.py +401 -0
  141. plain/utils/http.py +374 -0
  142. plain/utils/inspect.py +73 -0
  143. plain/utils/ipv6.py +46 -0
  144. plain/utils/itercompat.py +8 -0
  145. plain/utils/module_loading.py +69 -0
  146. plain/utils/regex_helper.py +353 -0
  147. plain/utils/safestring.py +72 -0
  148. plain/utils/termcolors.py +221 -0
  149. plain/utils/text.py +518 -0
  150. plain/utils/timesince.py +138 -0
  151. plain/utils/timezone.py +244 -0
  152. plain/utils/tree.py +126 -0
  153. plain/validators.py +603 -0
  154. plain/views/README.md +268 -0
  155. plain/views/__init__.py +18 -0
  156. plain/views/base.py +107 -0
  157. plain/views/csrf.py +24 -0
  158. plain/views/errors.py +25 -0
  159. plain/views/exceptions.py +4 -0
  160. plain/views/forms.py +76 -0
  161. plain/views/objects.py +229 -0
  162. plain/views/redirect.py +72 -0
  163. plain/views/templates.py +66 -0
  164. plain/wsgi.py +11 -0
  165. plain-0.1.0.dist-info/LICENSE +85 -0
  166. plain-0.1.0.dist-info/METADATA +51 -0
  167. plain-0.1.0.dist-info/RECORD +169 -0
  168. plain-0.1.0.dist-info/WHEEL +4 -0
  169. plain-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,456 @@
1
+ import copy
2
+ import itertools
3
+ import operator
4
+ from functools import total_ordering, wraps
5
+
6
+
7
+ class cached_property:
8
+ """
9
+ Decorator that converts a method with a single self argument into a
10
+ property cached on the instance.
11
+
12
+ A cached property can be made out of an existing method:
13
+ (e.g. ``url = cached_property(get_absolute_url)``).
14
+ """
15
+
16
+ name = None
17
+
18
+ @staticmethod
19
+ def func(instance):
20
+ raise TypeError(
21
+ "Cannot use cached_property instance without calling "
22
+ "__set_name__() on it."
23
+ )
24
+
25
+ def __init__(self, func):
26
+ self.real_func = func
27
+ self.__doc__ = getattr(func, "__doc__")
28
+
29
+ def __set_name__(self, owner, name):
30
+ if self.name is None:
31
+ self.name = name
32
+ self.func = self.real_func
33
+ elif name != self.name:
34
+ raise TypeError(
35
+ "Cannot assign the same cached_property to two different names "
36
+ f"({self.name!r} and {name!r})."
37
+ )
38
+
39
+ def __get__(self, instance, cls=None):
40
+ """
41
+ Call the function and put the return value in instance.__dict__ so that
42
+ subsequent attribute access on the instance returns the cached value
43
+ instead of calling cached_property.__get__().
44
+ """
45
+ if instance is None:
46
+ return self
47
+ res = instance.__dict__[self.name] = self.func(instance)
48
+ return res
49
+
50
+
51
+ class classproperty:
52
+ """
53
+ Decorator that converts a method with a single cls argument into a property
54
+ that can be accessed directly from the class.
55
+ """
56
+
57
+ def __init__(self, method=None):
58
+ self.fget = method
59
+
60
+ def __get__(self, instance, cls=None):
61
+ return self.fget(cls)
62
+
63
+ def getter(self, method):
64
+ self.fget = method
65
+ return self
66
+
67
+
68
+ class Promise:
69
+ """
70
+ Base class for the proxy class created in the closure of the lazy function.
71
+ It's used to recognize promises in code.
72
+ """
73
+
74
+ pass
75
+
76
+
77
+ def lazy(func, *resultclasses):
78
+ """
79
+ Turn any callable into a lazy evaluated callable. result classes or types
80
+ is required -- at least one is needed so that the automatic forcing of
81
+ the lazy evaluation code is triggered. Results are not memoized; the
82
+ function is evaluated on every access.
83
+ """
84
+
85
+ @total_ordering
86
+ class __proxy__(Promise):
87
+ """
88
+ Encapsulate a function call and act as a proxy for methods that are
89
+ called on the result of that function. The function is not evaluated
90
+ until one of the methods on the result is called.
91
+ """
92
+
93
+ __prepared = False
94
+
95
+ def __init__(self, args, kw):
96
+ self.__args = args
97
+ self.__kw = kw
98
+ if not self.__prepared:
99
+ self.__prepare_class__()
100
+ self.__class__.__prepared = True
101
+
102
+ def __reduce__(self):
103
+ return (
104
+ _lazy_proxy_unpickle,
105
+ (func, self.__args, self.__kw) + resultclasses,
106
+ )
107
+
108
+ def __repr__(self):
109
+ return repr(self.__cast())
110
+
111
+ @classmethod
112
+ def __prepare_class__(cls):
113
+ for resultclass in resultclasses:
114
+ for type_ in resultclass.mro():
115
+ for method_name in type_.__dict__:
116
+ # All __promise__ return the same wrapper method, they
117
+ # look up the correct implementation when called.
118
+ if hasattr(cls, method_name):
119
+ continue
120
+ meth = cls.__promise__(method_name)
121
+ setattr(cls, method_name, meth)
122
+ cls._delegate_bytes = bytes in resultclasses
123
+ cls._delegate_text = str in resultclasses
124
+ if cls._delegate_bytes and cls._delegate_text:
125
+ raise ValueError(
126
+ "Cannot call lazy() with both bytes and text return types."
127
+ )
128
+ if cls._delegate_text:
129
+ cls.__str__ = cls.__text_cast
130
+ elif cls._delegate_bytes:
131
+ cls.__bytes__ = cls.__bytes_cast
132
+
133
+ @classmethod
134
+ def __promise__(cls, method_name):
135
+ # Builds a wrapper around some magic method
136
+ def __wrapper__(self, *args, **kw):
137
+ # Automatically triggers the evaluation of a lazy value and
138
+ # applies the given magic method of the result type.
139
+ res = func(*self.__args, **self.__kw)
140
+ return getattr(res, method_name)(*args, **kw)
141
+
142
+ return __wrapper__
143
+
144
+ def __text_cast(self):
145
+ return func(*self.__args, **self.__kw)
146
+
147
+ def __bytes_cast(self):
148
+ return bytes(func(*self.__args, **self.__kw))
149
+
150
+ def __bytes_cast_encoded(self):
151
+ return func(*self.__args, **self.__kw).encode()
152
+
153
+ def __cast(self):
154
+ if self._delegate_bytes:
155
+ return self.__bytes_cast()
156
+ elif self._delegate_text:
157
+ return self.__text_cast()
158
+ else:
159
+ return func(*self.__args, **self.__kw)
160
+
161
+ def __str__(self):
162
+ # object defines __str__(), so __prepare_class__() won't overload
163
+ # a __str__() method from the proxied class.
164
+ return str(self.__cast())
165
+
166
+ def __eq__(self, other):
167
+ if isinstance(other, Promise):
168
+ other = other.__cast()
169
+ return self.__cast() == other
170
+
171
+ def __lt__(self, other):
172
+ if isinstance(other, Promise):
173
+ other = other.__cast()
174
+ return self.__cast() < other
175
+
176
+ def __hash__(self):
177
+ return hash(self.__cast())
178
+
179
+ def __mod__(self, rhs):
180
+ if self._delegate_text:
181
+ return str(self) % rhs
182
+ return self.__cast() % rhs
183
+
184
+ def __add__(self, other):
185
+ return self.__cast() + other
186
+
187
+ def __radd__(self, other):
188
+ return other + self.__cast()
189
+
190
+ def __deepcopy__(self, memo):
191
+ # Instances of this class are effectively immutable. It's just a
192
+ # collection of functions. So we don't need to do anything
193
+ # complicated for copying.
194
+ memo[id(self)] = self
195
+ return self
196
+
197
+ @wraps(func)
198
+ def __wrapper__(*args, **kw):
199
+ # Creates the proxy object, instead of the actual value.
200
+ return __proxy__(args, kw)
201
+
202
+ return __wrapper__
203
+
204
+
205
+ def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses):
206
+ return lazy(func, *resultclasses)(*args, **kwargs)
207
+
208
+
209
+ def lazystr(text):
210
+ """
211
+ Shortcut for the common case of a lazy callable that returns str.
212
+ """
213
+ return lazy(str, str)(text)
214
+
215
+
216
+ def keep_lazy(*resultclasses):
217
+ """
218
+ A decorator that allows a function to be called with one or more lazy
219
+ arguments. If none of the args are lazy, the function is evaluated
220
+ immediately, otherwise a __proxy__ is returned that will evaluate the
221
+ function when needed.
222
+ """
223
+ if not resultclasses:
224
+ raise TypeError("You must pass at least one argument to keep_lazy().")
225
+
226
+ def decorator(func):
227
+ lazy_func = lazy(func, *resultclasses)
228
+
229
+ @wraps(func)
230
+ def wrapper(*args, **kwargs):
231
+ if any(
232
+ isinstance(arg, Promise)
233
+ for arg in itertools.chain(args, kwargs.values())
234
+ ):
235
+ return lazy_func(*args, **kwargs)
236
+ return func(*args, **kwargs)
237
+
238
+ return wrapper
239
+
240
+ return decorator
241
+
242
+
243
+ def keep_lazy_text(func):
244
+ """
245
+ A decorator for functions that accept lazy arguments and return text.
246
+ """
247
+ return keep_lazy(str)(func)
248
+
249
+
250
+ empty = object()
251
+
252
+
253
+ def new_method_proxy(func):
254
+ def inner(self, *args):
255
+ if (_wrapped := self._wrapped) is empty:
256
+ self._setup()
257
+ _wrapped = self._wrapped
258
+ return func(_wrapped, *args)
259
+
260
+ inner._mask_wrapped = False
261
+ return inner
262
+
263
+
264
+ class LazyObject:
265
+ """
266
+ A wrapper for another class that can be used to delay instantiation of the
267
+ wrapped class.
268
+
269
+ By subclassing, you have the opportunity to intercept and alter the
270
+ instantiation. If you don't need to do that, use SimpleLazyObject.
271
+ """
272
+
273
+ # Avoid infinite recursion when tracing __init__ (#19456).
274
+ _wrapped = None
275
+
276
+ def __init__(self):
277
+ # Note: if a subclass overrides __init__(), it will likely need to
278
+ # override __copy__() and __deepcopy__() as well.
279
+ self._wrapped = empty
280
+
281
+ def __getattribute__(self, name):
282
+ if name == "_wrapped":
283
+ # Avoid recursion when getting wrapped object.
284
+ return super().__getattribute__(name)
285
+ value = super().__getattribute__(name)
286
+ # If attribute is a proxy method, raise an AttributeError to call
287
+ # __getattr__() and use the wrapped object method.
288
+ if not getattr(value, "_mask_wrapped", True):
289
+ raise AttributeError
290
+ return value
291
+
292
+ __getattr__ = new_method_proxy(getattr)
293
+
294
+ def __setattr__(self, name, value):
295
+ if name == "_wrapped":
296
+ # Assign to __dict__ to avoid infinite __setattr__ loops.
297
+ self.__dict__["_wrapped"] = value
298
+ else:
299
+ if self._wrapped is empty:
300
+ self._setup()
301
+ setattr(self._wrapped, name, value)
302
+
303
+ def __delattr__(self, name):
304
+ if name == "_wrapped":
305
+ raise TypeError("can't delete _wrapped.")
306
+ if self._wrapped is empty:
307
+ self._setup()
308
+ delattr(self._wrapped, name)
309
+
310
+ def _setup(self):
311
+ """
312
+ Must be implemented by subclasses to initialize the wrapped object.
313
+ """
314
+ raise NotImplementedError(
315
+ "subclasses of LazyObject must provide a _setup() method"
316
+ )
317
+
318
+ # Because we have messed with __class__ below, we confuse pickle as to what
319
+ # class we are pickling. We're going to have to initialize the wrapped
320
+ # object to successfully pickle it, so we might as well just pickle the
321
+ # wrapped object since they're supposed to act the same way.
322
+ #
323
+ # Unfortunately, if we try to simply act like the wrapped object, the ruse
324
+ # will break down when pickle gets our id(). Thus we end up with pickle
325
+ # thinking, in effect, that we are a distinct object from the wrapped
326
+ # object, but with the same __dict__. This can cause problems (see #25389).
327
+ #
328
+ # So instead, we define our own __reduce__ method and custom unpickler. We
329
+ # pickle the wrapped object as the unpickler's argument, so that pickle
330
+ # will pickle it normally, and then the unpickler simply returns its
331
+ # argument.
332
+ def __reduce__(self):
333
+ if self._wrapped is empty:
334
+ self._setup()
335
+ return (unpickle_lazyobject, (self._wrapped,))
336
+
337
+ def __copy__(self):
338
+ if self._wrapped is empty:
339
+ # If uninitialized, copy the wrapper. Use type(self), not
340
+ # self.__class__, because the latter is proxied.
341
+ return type(self)()
342
+ else:
343
+ # If initialized, return a copy of the wrapped object.
344
+ return copy.copy(self._wrapped)
345
+
346
+ def __deepcopy__(self, memo):
347
+ if self._wrapped is empty:
348
+ # We have to use type(self), not self.__class__, because the
349
+ # latter is proxied.
350
+ result = type(self)()
351
+ memo[id(self)] = result
352
+ return result
353
+ return copy.deepcopy(self._wrapped, memo)
354
+
355
+ __bytes__ = new_method_proxy(bytes)
356
+ __str__ = new_method_proxy(str)
357
+ __bool__ = new_method_proxy(bool)
358
+
359
+ # Introspection support
360
+ __dir__ = new_method_proxy(dir)
361
+
362
+ # Need to pretend to be the wrapped class, for the sake of objects that
363
+ # care about this (especially in equality tests)
364
+ __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
365
+ __eq__ = new_method_proxy(operator.eq)
366
+ __lt__ = new_method_proxy(operator.lt)
367
+ __gt__ = new_method_proxy(operator.gt)
368
+ __ne__ = new_method_proxy(operator.ne)
369
+ __hash__ = new_method_proxy(hash)
370
+
371
+ # List/Tuple/Dictionary methods support
372
+ __getitem__ = new_method_proxy(operator.getitem)
373
+ __setitem__ = new_method_proxy(operator.setitem)
374
+ __delitem__ = new_method_proxy(operator.delitem)
375
+ __iter__ = new_method_proxy(iter)
376
+ __len__ = new_method_proxy(len)
377
+ __contains__ = new_method_proxy(operator.contains)
378
+
379
+
380
+ def unpickle_lazyobject(wrapped):
381
+ """
382
+ Used to unpickle lazy objects. Just return its argument, which will be the
383
+ wrapped object.
384
+ """
385
+ return wrapped
386
+
387
+
388
+ class SimpleLazyObject(LazyObject):
389
+ """
390
+ A lazy object initialized from any function.
391
+
392
+ Designed for compound objects of unknown type. For builtins or objects of
393
+ known type, use plain.utils.functional.lazy.
394
+ """
395
+
396
+ def __init__(self, func):
397
+ """
398
+ Pass in a callable that returns the object to be wrapped.
399
+
400
+ If copies are made of the resulting SimpleLazyObject, which can happen
401
+ in various circumstances within Plain, then you must ensure that the
402
+ callable can be safely run more than once and will return the same
403
+ value.
404
+ """
405
+ self.__dict__["_setupfunc"] = func
406
+ super().__init__()
407
+
408
+ def _setup(self):
409
+ self._wrapped = self._setupfunc()
410
+
411
+ # Return a meaningful representation of the lazy object for debugging
412
+ # without evaluating the wrapped object.
413
+ def __repr__(self):
414
+ if self._wrapped is empty:
415
+ repr_attr = self._setupfunc
416
+ else:
417
+ repr_attr = self._wrapped
418
+ return f"<{type(self).__name__}: {repr_attr!r}>"
419
+
420
+ def __copy__(self):
421
+ if self._wrapped is empty:
422
+ # If uninitialized, copy the wrapper. Use SimpleLazyObject, not
423
+ # self.__class__, because the latter is proxied.
424
+ return SimpleLazyObject(self._setupfunc)
425
+ else:
426
+ # If initialized, return a copy of the wrapped object.
427
+ return copy.copy(self._wrapped)
428
+
429
+ def __deepcopy__(self, memo):
430
+ if self._wrapped is empty:
431
+ # We have to use SimpleLazyObject, not self.__class__, because the
432
+ # latter is proxied.
433
+ result = SimpleLazyObject(self._setupfunc)
434
+ memo[id(self)] = result
435
+ return result
436
+ return copy.deepcopy(self._wrapped, memo)
437
+
438
+ __add__ = new_method_proxy(operator.add)
439
+
440
+ @new_method_proxy
441
+ def __radd__(self, other):
442
+ return other + self
443
+
444
+
445
+ def partition(predicate, values):
446
+ """
447
+ Split the values into two sets, based on the return value of the function
448
+ (True/False). e.g.:
449
+
450
+ >>> partition(lambda x: x > 3, range(5))
451
+ [0, 1, 2, 3], [4]
452
+ """
453
+ results = ([], [])
454
+ for item in values:
455
+ results[predicate(item)].append(item)
456
+ return results
@@ -0,0 +1,26 @@
1
+ from plain.utils.itercompat import is_iterable
2
+
3
+
4
+ def make_hashable(value):
5
+ """
6
+ Attempt to make value hashable or raise a TypeError if it fails.
7
+
8
+ The returned value should generate the same hash for equal values.
9
+ """
10
+ if isinstance(value, dict):
11
+ return tuple(
12
+ [
13
+ (key, make_hashable(nested_value))
14
+ for key, nested_value in sorted(value.items())
15
+ ]
16
+ )
17
+ # Try hash to avoid converting a hashable iterable (e.g. string, frozenset)
18
+ # to a tuple.
19
+ try:
20
+ hash(value)
21
+ except TypeError:
22
+ if is_iterable(value):
23
+ return tuple(map(make_hashable, value))
24
+ # Non-hashable, non-iterable.
25
+ raise
26
+ return value