-
Notifications
You must be signed in to change notification settings - Fork 575
/
types.py
952 lines (828 loc) · 35.3 KB
/
types.py
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.
import builtins
import collections
import collections.abc
import datetime
import decimal
import fractions
import functools
import inspect
import io
import ipaddress
import numbers
import os
import random
import re
import sys
import typing
import uuid
from pathlib import PurePath
from types import FunctionType
from typing import get_args, get_origin
from hypothesis import strategies as st
from hypothesis.errors import InvalidArgument, ResolutionFailed
from hypothesis.internal.compat import PYPY, BaseExceptionGroup, ExceptionGroup
from hypothesis.internal.conjecture.utils import many as conjecture_utils_many
from hypothesis.strategies._internal.datetime import zoneinfo # type: ignore
from hypothesis.strategies._internal.ipaddress import (
SPECIAL_IPv4_RANGES,
SPECIAL_IPv6_RANGES,
ip_addresses,
)
from hypothesis.strategies._internal.lazy import unwrap_strategies
from hypothesis.strategies._internal.strategies import OneOfStrategy
GenericAlias: typing.Any
UnionType: typing.Any
try:
# The type of PEP-604 unions (`int | str`), added in Python 3.10
from types import GenericAlias, UnionType
except ImportError:
GenericAlias = ()
UnionType = ()
try:
import typing_extensions
except ImportError:
typing_extensions = None # type: ignore
try:
from typing import _AnnotatedAlias # type: ignore
except ImportError:
try:
from typing_extensions import _AnnotatedAlias
except ImportError:
_AnnotatedAlias = ()
TypeAliasTypes: tuple = ()
try:
TypeAliasTypes += (typing.TypeAlias,)
except AttributeError: # pragma: no cover
pass # Is missing for `python<3.10`
try:
TypeAliasTypes += (typing_extensions.TypeAlias,)
except AttributeError: # pragma: no cover
pass # Is missing for `typing_extensions<3.10`
ClassVarTypes: tuple = (typing.ClassVar,)
try:
ClassVarTypes += (typing_extensions.ClassVar,)
except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
FinalTypes: tuple = (typing.Final,)
try:
FinalTypes += (typing_extensions.Final,)
except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
ConcatenateTypes: tuple = ()
try:
ConcatenateTypes += (typing.Concatenate,)
except AttributeError: # pragma: no cover
pass # Is missing for `python<3.10`
try:
ConcatenateTypes += (typing_extensions.Concatenate,)
except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
ParamSpecTypes: tuple = ()
try:
ParamSpecTypes += (typing.ParamSpec,)
except AttributeError: # pragma: no cover
pass # Is missing for `python<3.10`
try:
ParamSpecTypes += (typing_extensions.ParamSpec,)
except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
TypeGuardTypes: tuple = ()
try:
TypeGuardTypes += (typing.TypeGuard,)
except AttributeError: # pragma: no cover
pass # Is missing for `python<3.10`
try:
TypeGuardTypes += (typing_extensions.TypeGuard,)
except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
RequiredTypes: tuple = ()
try:
RequiredTypes += (typing.Required,) # type: ignore
except AttributeError: # pragma: no cover
pass # Is missing for `python<3.11`
try:
RequiredTypes += (typing_extensions.Required,)
except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
NotRequiredTypes: tuple = ()
try:
NotRequiredTypes += (typing.NotRequired,) # type: ignore
except AttributeError: # pragma: no cover
pass # Is missing for `python<3.11`
try:
NotRequiredTypes += (typing_extensions.NotRequired,)
except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
# We use this variable to be sure that we are working with a type from `typing`:
typing_root_type = (typing._Final, typing._GenericAlias) # type: ignore
# We use this to disallow all non-runtime types from being registered and resolved.
# By "non-runtime" we mean: types that do not really exist in python's
# and are just added for more fancy type annotations.
# `Final` is a great example: it just indicates that this value can't be reassigned.
NON_RUNTIME_TYPES = (
typing.Any,
*ClassVarTypes,
*TypeAliasTypes,
*FinalTypes,
*ConcatenateTypes,
*ParamSpecTypes,
*TypeGuardTypes,
)
for name in (
"Annotated",
"NoReturn",
"Self",
"Required",
"NotRequired",
"Never",
"TypeVarTuple",
"Unpack",
"LiteralString",
):
try:
NON_RUNTIME_TYPES += (getattr(typing, name),)
except AttributeError:
pass
try:
NON_RUNTIME_TYPES += (getattr(typing_extensions, name),)
except AttributeError: # pragma: no cover
pass # typing_extensions might not be installed
def type_sorting_key(t):
"""Minimise to None, then non-container types, then container types."""
if t is None or t is type(None):
return (-1, repr(t))
t = get_origin(t) or t
try:
is_container = int(issubclass(t, collections.abc.Container))
except Exception: # pragma: no cover
# e.g. `typing_extensions.Literal` is not a container
is_container = 0
return (is_container, repr(t))
def _compatible_args(args, superclass_args):
"""Check that the args of two generic types are compatible for try_issubclass."""
assert superclass_args is not None
if args is None:
return True
return len(args) == len(superclass_args) and all(
# "a==b or either is a typevar" is a hacky approximation, but it's
# good enough for all the cases that I've seen so far and has the
# substantial virtue of (relative) simplicity.
a == b or isinstance(a, typing.TypeVar) or isinstance(b, typing.TypeVar)
for a, b in zip(args, superclass_args)
)
def try_issubclass(thing, superclass):
try:
# In this case we're looking at two distinct classes - which might be generics.
# That brings in some complications:
if issubclass(get_origin(thing) or thing, get_origin(superclass) or superclass):
superclass_args = get_args(superclass)
if not superclass_args:
# The superclass is not generic, so we're definitely a subclass.
return True
# Sadly this is just some really fiddly logic to handle all the cases
# of user-defined generic types, types inheriting from parametrised
# generics, and so on. If you need to change this code, read PEP-560
# and Hypothesis issue #2951 closely first, and good luck. The tests
# will help you, I hope - good luck.
if getattr(thing, "__args__", None) is not None:
return True # pragma: no cover # only possible on Python <= 3.9
for orig_base in getattr(thing, "__orig_bases__", None) or [None]:
args = getattr(orig_base, "__args__", None)
if _compatible_args(args, superclass_args):
return True
return False
except (AttributeError, TypeError):
# Some types can't be the subject or object of an instance or subclass check
return False
def is_a_new_type(thing):
if not isinstance(typing.NewType, type):
# At runtime, `typing.NewType` returns an identity function rather
# than an actual type, but we can check whether that thing matches.
return ( # pragma: no cover # Python <= 3.9 only
hasattr(thing, "__supertype__")
and getattr(thing, "__module__", None) in ("typing", "typing_extensions")
and inspect.isfunction(thing)
)
# In 3.10 and later, NewType is actually a class - which simplifies things.
# See https://bugs.python.org/issue44353 for links to the various patches.
return isinstance(thing, typing.NewType)
def is_a_union(thing):
"""Return True if thing is a typing.Union or types.UnionType (in py310)."""
return isinstance(thing, UnionType) or get_origin(thing) is typing.Union
def is_a_type(thing):
"""Return True if thing is a type or a generic type like thing."""
return isinstance(thing, type) or is_generic_type(thing) or is_a_new_type(thing)
def is_typing_literal(thing):
return get_origin(thing) in (
typing.Literal,
getattr(typing_extensions, "Literal", object()),
)
def is_annotated_type(thing):
return (
isinstance(thing, _AnnotatedAlias)
and getattr(thing, "__args__", None) is not None
)
def find_annotated_strategy(annotated_type): # pragma: no cover
flattened_meta = []
all_args = (
*getattr(annotated_type, "__args__", ()),
*getattr(annotated_type, "__metadata__", ()),
)
for arg in all_args:
if is_annotated_type(arg):
flattened_meta.append(find_annotated_strategy(arg))
if isinstance(arg, st.SearchStrategy):
flattened_meta.append(arg)
return flattened_meta[-1] if flattened_meta else None
def has_type_arguments(type_):
"""Decides whethere or not this type has applied type arguments."""
args = getattr(type_, "__args__", None)
if args and isinstance(type_, (typing._GenericAlias, GenericAlias)):
# There are some cases when declared types do already have type arguments
# Like `Sequence`, that is `_GenericAlias(abc.Sequence[T])[T]`
parameters = getattr(type_, "__parameters__", None)
if parameters: # So, we need to know if type args are just "aliases"
return args != parameters
return bool(args)
def is_generic_type(type_):
"""Decides whether a given type is generic or not."""
# The ugly truth is that `MyClass`, `MyClass[T]`, and `MyClass[int]` are very different.
# We check for `MyClass[T]` and `MyClass[int]` with the first condition,
# while the second condition is for `MyClass`.
return isinstance(type_, (*typing_root_type, GenericAlias)) or (
isinstance(type_, type)
and (typing.Generic in type_.__mro__ or hasattr(type_, "__class_getitem__"))
)
def _try_import_forward_ref(thing, bound): # pragma: no cover
"""
Tries to import a real bound type from ``TypeVar`` bound to a ``ForwardRef``.
This function is very "magical" to say the least, please don't use it.
This function fully covered, but is excluded from coverage
because we can only cover each path in a separate python version.
"""
try:
return typing._eval_type(bound, vars(sys.modules[thing.__module__]), None)
except (KeyError, AttributeError, NameError):
# We fallback to `ForwardRef` instance, you can register it as a type as well:
# >>> from typing import ForwardRef
# >>> from hypothesis import strategies as st
# >>> st.register_type_strategy(ForwardRef('YourType'), your_strategy)
return bound
def from_typing_type(thing):
# We start with special-case support for Tuple, which isn't actually a generic
# type; then Final, Literal, and Annotated since they don't support `isinstance`.
#
# We then explicitly error on non-Generic types, which don't carry enough
# information to sensibly resolve to strategies at runtime.
# Finally, we run a variation of the subclass lookup in `st.from_type`
# among generic types in the lookup.
if get_origin(thing) == tuple or isinstance(
thing, getattr(typing, "TupleMeta", ())
):
elem_types = getattr(thing, "__tuple_params__", None) or ()
elem_types += getattr(thing, "__args__", None) or ()
if (
getattr(thing, "__tuple_use_ellipsis__", False)
or len(elem_types) == 2
and elem_types[-1] is Ellipsis
):
return st.lists(st.from_type(elem_types[0])).map(tuple)
elif len(elem_types) == 1 and elem_types[0] == ():
return st.tuples() # Empty tuple; see issue #1583
return st.tuples(*map(st.from_type, elem_types))
if get_origin(thing) == typing.Final:
return st.one_of([st.from_type(t) for t in thing.__args__])
if is_typing_literal(thing):
args_dfs_stack = list(thing.__args__)
literals = []
while args_dfs_stack:
arg = args_dfs_stack.pop()
if is_typing_literal(arg): # pragma: no cover
# Python 3.10+ flattens for us when constructing Literal objects
args_dfs_stack.extend(reversed(arg.__args__))
else:
literals.append(arg)
return st.sampled_from(literals)
if is_annotated_type(thing): # pragma: no cover
# This requires Python 3.9+ or the typing_extensions package
annotated_strategy = find_annotated_strategy(thing)
if annotated_strategy is not None:
return annotated_strategy
args = thing.__args__
assert args, "it's impossible to make an annotated type with no args"
annotated_type = args[0]
return st.from_type(annotated_type)
# Now, confirm that we're dealing with a generic type as we expected
if sys.version_info[:2] < (3, 9) and not isinstance(
thing, typing_root_type
): # pragma: no cover
raise ResolutionFailed(f"Cannot resolve {thing} to a strategy")
# Some "generic" classes are not generic *in* anything - for example both
# Hashable and Sized have `__args__ == ()`
origin = get_origin(thing) or thing
if (
origin in vars(collections.abc).values()
and len(getattr(thing, "__args__", None) or []) == 0
):
return st.from_type(origin)
# Parametrised generic types have their __origin__ attribute set to the
# un-parametrised version, which we need to use in the subclass checks.
# e.g.: typing.List[int].__origin__ == typing.List
# (actually not sure if this is true since Python 3.9 or so)
mapping = {
k: v
for k, v in _global_type_lookup.items()
if is_generic_type(k) and try_issubclass(k, thing)
}
# Drop some unusual cases for simplicity
for weird in (tuple, getattr(os, "_Environ", None)):
if len(mapping) > 1:
mapping.pop(weird, None)
# After we drop Python 3.8 and can rely on having generic builtin types, we'll
# be able to simplify this logic by dropping the typing-module handling.
if {dict, set, typing.Dict, typing.Set}.intersection(mapping):
# ItemsView can cause test_lookup.py::test_specialised_collection_types
# to fail, due to weird isinstance behaviour around the elements.
mapping.pop(collections.abc.ItemsView, None)
mapping.pop(typing.ItemsView, None)
if {collections.deque, typing.Deque}.intersection(mapping) and len(mapping) > 1:
# Resolving generic sequences to include a deque is more trouble for e.g.
# the ghostwriter than it's worth, via undefined names in the repr.
mapping.pop(collections.deque, None)
mapping.pop(typing.Deque, None)
if len(mapping) > 1:
# issubclass treats bytestring as a kind of sequence, which it is,
# but treating it as such breaks everything else when it is presumed
# to be a generic sequence or container that could hold any item.
# Except for sequences of integers, or unions which include integer!
# See https://github.com/HypothesisWorks/hypothesis/issues/2257
#
# This block drops bytes from the types that can be generated
# if there is more than one allowed type, and the element type is
# not either `int` or a Union with `int` as one of its elements.
elem_type = (getattr(thing, "__args__", None) or ["not int"])[0]
if is_a_union(elem_type):
union_elems = elem_type.__args__
else:
union_elems = ()
if not any(
isinstance(T, type) and issubclass(int, get_origin(T) or T)
for T in [*union_elems, elem_type]
):
mapping.pop(bytes, None)
mapping.pop(collections.abc.ByteString, None)
mapping.pop(typing.ByteString, None)
elif (
(not mapping)
and isinstance(thing, typing.ForwardRef)
and thing.__forward_arg__ in vars(builtins)
):
return st.from_type(getattr(builtins, thing.__forward_arg__))
# Before Python 3.9, we sometimes have e.g. Sequence from both the typing
# module, and collections.abc module. Discard any type which is not it's own
# origin, where the origin is also in the mapping.
for t in sorted(mapping, key=type_sorting_key):
origin = get_origin(t)
if origin is not t and origin in mapping:
mapping.pop(t)
# Sort strategies according to our type-sorting heuristic for stable output
strategies = [
v if isinstance(v, st.SearchStrategy) else v(thing)
for k, v in sorted(mapping.items(), key=lambda kv: type_sorting_key(kv[0]))
if sum(try_issubclass(k, T) for T in mapping) == 1
]
empty = ", ".join(repr(s) for s in strategies if s.is_empty)
if empty or not strategies:
raise ResolutionFailed(
f"Could not resolve {empty or thing} to a strategy; "
"consider using register_type_strategy"
)
return st.one_of(strategies)
def can_cast(type, value):
"""Determine if value can be cast to type."""
try:
type(value)
return True
except Exception:
return False
def _networks(bits):
return st.tuples(st.integers(0, 2**bits - 1), st.integers(-bits, 0).map(abs))
utc_offsets = st.builds(
datetime.timedelta, minutes=st.integers(0, 59), hours=st.integers(-23, 23)
)
# These builtin and standard-library types have Hypothesis strategies,
# seem likely to appear in type annotations, or are otherwise notable.
#
# The strategies below must cover all possible values from the type, because
# many users treat them as comprehensive and one of Hypothesis' design goals
# is to avoid testing less than expected.
#
# As a general rule, we try to limit this to scalars because from_type()
# would have to decide on arbitrary collection elements, and we'd rather
# not (with typing module generic types and some builtins as exceptions).
_global_type_lookup: typing.Dict[
type, typing.Union[st.SearchStrategy, typing.Callable[[type], st.SearchStrategy]]
] = {
type(None): st.none(),
bool: st.booleans(),
int: st.integers(),
float: st.floats(),
complex: st.complex_numbers(),
fractions.Fraction: st.fractions(),
decimal.Decimal: st.decimals(),
str: st.text(),
bytes: st.binary(),
datetime.datetime: st.datetimes(),
datetime.date: st.dates(),
datetime.time: st.times(),
datetime.timedelta: st.timedeltas(),
datetime.timezone: st.builds(datetime.timezone, offset=utc_offsets)
| st.builds(datetime.timezone, offset=utc_offsets, name=st.text(st.characters())),
uuid.UUID: st.uuids(),
tuple: st.builds(tuple),
list: st.builds(list),
set: st.builds(set),
collections.abc.MutableSet: st.builds(set),
frozenset: st.builds(frozenset),
dict: st.builds(dict),
FunctionType: st.functions(),
type(Ellipsis): st.just(Ellipsis),
type(NotImplemented): st.just(NotImplemented),
bytearray: st.binary().map(bytearray),
memoryview: st.binary().map(memoryview),
numbers.Real: st.floats(),
numbers.Rational: st.fractions(),
numbers.Number: st.complex_numbers(),
numbers.Integral: st.integers(),
numbers.Complex: st.complex_numbers(),
slice: st.builds(
slice,
st.none() | st.integers(),
st.none() | st.integers(),
st.none() | st.integers(),
),
range: st.one_of(
st.builds(range, st.integers(min_value=0)),
st.builds(range, st.integers(), st.integers()),
st.builds(range, st.integers(), st.integers(), st.integers().filter(bool)),
),
ipaddress.IPv4Address: ip_addresses(v=4),
ipaddress.IPv6Address: ip_addresses(v=6),
ipaddress.IPv4Interface: _networks(32).map(ipaddress.IPv4Interface),
ipaddress.IPv6Interface: _networks(128).map(ipaddress.IPv6Interface),
ipaddress.IPv4Network: st.one_of(
_networks(32).map(lambda x: ipaddress.IPv4Network(x, strict=False)),
st.sampled_from(SPECIAL_IPv4_RANGES).map(ipaddress.IPv4Network),
),
ipaddress.IPv6Network: st.one_of(
_networks(128).map(lambda x: ipaddress.IPv6Network(x, strict=False)),
st.sampled_from(SPECIAL_IPv6_RANGES).map(ipaddress.IPv6Network),
),
os.PathLike: st.builds(PurePath, st.text()),
UnicodeDecodeError: st.builds(
UnicodeDecodeError,
st.just("unknown encoding"),
st.just(b""),
st.just(0),
st.just(0),
st.just("reason"),
),
UnicodeEncodeError: st.builds(
UnicodeEncodeError,
st.just("unknown encoding"),
st.text(),
st.just(0),
st.just(0),
st.just("reason"),
),
UnicodeTranslateError: st.builds(
UnicodeTranslateError, st.text(), st.just(0), st.just(0), st.just("reason")
),
BaseExceptionGroup: st.builds(
BaseExceptionGroup,
st.text(),
st.lists(st.from_type(BaseException), min_size=1),
),
ExceptionGroup: st.builds(
ExceptionGroup,
st.text(),
st.lists(st.from_type(Exception), min_size=1),
),
enumerate: st.builds(enumerate, st.just(())),
filter: st.builds(filter, st.just(lambda _: None), st.just(())),
map: st.builds(map, st.just(lambda _: None), st.just(())),
reversed: st.builds(reversed, st.just(())),
property: st.builds(property, st.just(lambda _: None)),
classmethod: st.builds(classmethod, st.just(lambda self: self)),
staticmethod: st.builds(staticmethod, st.just(lambda self: self)),
super: st.builds(super, st.from_type(type)),
re.Match: st.text().map(lambda c: re.match(".", c, flags=re.DOTALL)).filter(bool),
re.Pattern: st.builds(re.compile, st.sampled_from(["", b""])),
random.Random: st.randoms(),
# Pull requests with more types welcome!
}
if zoneinfo is not None: # pragma: no branch
_global_type_lookup[zoneinfo.ZoneInfo] = st.timezones()
if PYPY:
_global_type_lookup[builtins.sequenceiterator] = st.builds(iter, st.tuples()) # type: ignore
_global_type_lookup[type] = st.sampled_from(
[type(None), *sorted(_global_type_lookup, key=str)]
)
if sys.version_info[:2] >= (3, 9):
# subclass of MutableMapping, and in Python 3.9 we resolve to a union
# which includes this... but we don't actually ever want to build one.
_global_type_lookup[os._Environ] = st.just(os.environ)
_global_type_lookup.update(
{
# Note: while ByteString notionally also represents the bytearray and
# memoryview types, it is a subclass of Hashable and those types are not.
# We therefore only generate the bytes type. type-ignored due to deprecation.
typing.ByteString: st.binary(), # type: ignore
collections.abc.ByteString: st.binary(), # type: ignore
# TODO: SupportsAbs and SupportsRound should be covariant, ie have functions.
typing.SupportsAbs: st.one_of(
st.booleans(),
st.integers(),
st.floats(),
st.complex_numbers(),
st.fractions(),
st.decimals(),
st.timedeltas(),
),
typing.SupportsRound: st.one_of(
st.booleans(), st.integers(), st.floats(), st.decimals(), st.fractions()
),
typing.SupportsComplex: st.one_of(
st.booleans(),
st.integers(),
st.floats(),
st.complex_numbers(),
st.decimals(),
st.fractions(),
),
typing.SupportsFloat: st.one_of(
st.booleans(),
st.integers(),
st.floats(),
st.decimals(),
st.fractions(),
# with floats its far more annoying to capture all
# the magic in a regex. so we just stringify some floats
st.floats().map(str),
),
typing.SupportsInt: st.one_of(
st.booleans(),
st.integers(),
st.floats(),
st.uuids(),
st.decimals(),
# this generates strings that should able to be parsed into integers
st.from_regex(r"\A-?\d+\Z").filter(functools.partial(can_cast, int)),
),
typing.SupportsIndex: st.integers() | st.booleans(),
typing.SupportsBytes: st.one_of(
st.booleans(),
st.binary(),
st.integers(0, 255),
# As with Reversible, we tuplize this for compatibility with Hashable.
st.lists(st.integers(0, 255)).map(tuple), # type: ignore
),
typing.BinaryIO: st.builds(io.BytesIO, st.binary()),
typing.TextIO: st.builds(io.StringIO, st.text()),
}
)
# The "extra" lookups define a callable that either resolves to a strategy for
# this narrowly extra-specific type, or returns None to proceed with normal
# type resolution. The callable will only be called if the module is
# installed. To avoid the performance hit of importing anything here, we defer
# it until the method is called the first time, at which point we replace the
# entry in the lookup table with the direct call.
def _from_numpy_type(thing: typing.Type) -> typing.Optional[st.SearchStrategy]:
from hypothesis.extra.numpy import _from_type
_global_extra_lookup["numpy"] = _from_type
return _from_type(thing)
_global_extra_lookup: typing.Dict[
str, typing.Callable[[typing.Type], typing.Optional[st.SearchStrategy]]
] = {
"numpy": _from_numpy_type,
}
def register(type_, fallback=None, *, module=typing):
if isinstance(type_, str):
# Use the name of generic types which are not available on all
# versions, and the function just won't be added to the registry
type_ = getattr(module, type_, None)
if type_ is None: # pragma: no cover
return lambda f: f
def inner(func):
nonlocal type_
if fallback is None:
_global_type_lookup[type_] = func
return func
@functools.wraps(func)
def really_inner(thing):
# This branch is for Python <= 3.8, when __args__ was not always tracked
if getattr(thing, "__args__", None) is None:
return fallback # pragma: no cover
return func(thing)
if sys.version_info[:2] >= (3, 9):
try:
type_ = get_origin(type_)
except Exception:
pass
_global_type_lookup[type_] = really_inner
return really_inner
return inner
@register(typing.Type)
@register("Type", module=typing_extensions)
def resolve_Type(thing):
if getattr(thing, "__args__", None) is None:
# This branch is for Python <= 3.8, when __args__ was not always tracked
return st.just(type) # pragma: no cover
args = (thing.__args__[0],)
if is_a_union(args[0]):
args = args[0].__args__
# Duplicate check from from_type here - only paying when needed.
args = list(args)
for i, a in enumerate(args):
if type(a) == typing.ForwardRef:
try:
args[i] = getattr(builtins, a.__forward_arg__)
except AttributeError:
raise ResolutionFailed(
f"Cannot find the type referenced by {thing} - try using "
f"st.register_type_strategy({thing}, st.from_type(...))"
) from None
return st.sampled_from(sorted(args, key=type_sorting_key))
@register(typing.List, st.builds(list))
def resolve_List(thing):
return st.lists(st.from_type(thing.__args__[0]))
def _can_hash(val):
try:
hash(val)
return True
except Exception:
return False
# Some types are subclasses of typing.Hashable, because they define a __hash__
# method, but have non-hashable instances such as `Decimal("snan")` or may contain
# such instances (e.g. `FrozenSet[Decimal]`). We therefore keep this whitelist of
# types which are always hashable, and apply the `_can_hash` filter to all others.
# Our goal is not completeness, it's to get a small performance boost for the most
# common cases, and a short whitelist is basically free to maintain.
ALWAYS_HASHABLE_TYPES = {type(None), bool, int, float, complex, str, bytes}
def _from_hashable_type(type_):
if type_ in ALWAYS_HASHABLE_TYPES:
return st.from_type(type_)
else:
return st.from_type(type_).filter(_can_hash)
@register(typing.Set, st.builds(set))
@register(typing.MutableSet, st.builds(set))
def resolve_Set(thing):
return st.sets(_from_hashable_type(thing.__args__[0]))
@register(typing.FrozenSet, st.builds(frozenset))
def resolve_FrozenSet(thing):
return st.frozensets(_from_hashable_type(thing.__args__[0]))
@register(typing.Dict, st.builds(dict))
def resolve_Dict(thing):
# If thing is a Collection instance, we need to fill in the values
keys_vals = thing.__args__ * 2
return st.dictionaries(
_from_hashable_type(keys_vals[0]), st.from_type(keys_vals[1])
)
@register(typing.DefaultDict, st.builds(collections.defaultdict))
@register("DefaultDict", st.builds(collections.defaultdict), module=typing_extensions)
def resolve_DefaultDict(thing):
return resolve_Dict(thing).map(lambda d: collections.defaultdict(None, d))
@register(typing.ItemsView, st.builds(dict).map(dict.items))
def resolve_ItemsView(thing):
return resolve_Dict(thing).map(dict.items)
@register(typing.KeysView, st.builds(dict).map(dict.keys))
def resolve_KeysView(thing):
return st.dictionaries(_from_hashable_type(thing.__args__[0]), st.none()).map(
dict.keys
)
@register(typing.ValuesView, st.builds(dict).map(dict.values))
def resolve_ValuesView(thing):
return st.dictionaries(st.integers(), st.from_type(thing.__args__[0])).map(
dict.values
)
@register(typing.Iterator, st.iterables(st.nothing()))
def resolve_Iterator(thing):
return st.iterables(st.from_type(thing.__args__[0]))
@register(typing.Counter, st.builds(collections.Counter))
def resolve_Counter(thing):
return st.dictionaries(
keys=st.from_type(thing.__args__[0]),
values=st.integers(),
).map(collections.Counter)
@register(typing.Deque, st.builds(collections.deque))
def resolve_deque(thing):
return st.lists(st.from_type(thing.__args__[0])).map(collections.deque)
@register(typing.ChainMap, st.builds(dict).map(collections.ChainMap))
def resolve_ChainMap(thing):
return resolve_Dict(thing).map(collections.ChainMap)
@register(typing.OrderedDict, st.builds(dict).map(collections.OrderedDict))
def resolve_OrderedDict(thing):
return resolve_Dict(thing).map(collections.OrderedDict)
@register(typing.Pattern, st.builds(re.compile, st.sampled_from(["", b""])))
def resolve_Pattern(thing):
if isinstance(thing.__args__[0], typing.TypeVar): # pragma: no cover
# TODO: this was covered on Python 3.8, but isn't on 3.10 - we should
# work out why not and write some extra tests to help avoid regressions.
return st.builds(re.compile, st.sampled_from(["", b""]))
return st.just(re.compile(thing.__args__[0]()))
@register( # pragma: no branch # coverage does not see lambda->exit branch
typing.Match,
st.text().map(lambda c: re.match(".", c, flags=re.DOTALL)).filter(bool),
)
def resolve_Match(thing):
if thing.__args__[0] == bytes:
return (
st.binary(min_size=1)
.map(lambda c: re.match(b".", c, flags=re.DOTALL))
.filter(bool)
)
return st.text().map(lambda c: re.match(".", c, flags=re.DOTALL)).filter(bool)
class GeneratorStrategy(st.SearchStrategy):
def __init__(self, yields, returns):
assert isinstance(yields, st.SearchStrategy)
assert isinstance(returns, st.SearchStrategy)
self.yields = yields
self.returns = returns
def __repr__(self):
return f"<generators yields={self.yields!r} returns={self.returns!r}>"
def do_draw(self, data):
elements = conjecture_utils_many(data, min_size=0, max_size=100, average_size=5)
while elements.more():
yield data.draw(self.yields)
return data.draw(self.returns)
@register(typing.Generator, GeneratorStrategy(st.none(), st.none()))
def resolve_Generator(thing):
yields, _, returns = thing.__args__
return GeneratorStrategy(st.from_type(yields), st.from_type(returns))
@register(typing.Callable, st.functions())
def resolve_Callable(thing):
# Generated functions either accept no arguments, or arbitrary arguments.
# This is looser than ideal, but anything tighter would generally break
# use of keyword arguments and we'd rather not force positional-only.
if not thing.__args__: # pragma: no cover # varies by minor version
return st.functions()
*args_types, return_type = thing.__args__
# Note that a list can only appear in __args__ under Python 3.9 with the
# collections.abc version; see https://bugs.python.org/issue42195
if len(args_types) == 1 and isinstance(args_types[0], list):
args_types = tuple(args_types[0]) # pragma: no cover
pep612 = ConcatenateTypes + ParamSpecTypes
for arg in args_types:
# awkward dance because you can't use Concatenate in isistance or issubclass
if getattr(arg, "__origin__", arg) in pep612 or type(arg) in pep612:
raise InvalidArgument(
"Hypothesis can't yet construct a strategy for instances of a "
f"Callable type parametrized by {arg!r}. Consider using an "
"explicit strategy, or opening an issue."
)
if get_origin(return_type) in TypeGuardTypes:
raise InvalidArgument(
"Hypothesis cannot yet construct a strategy for callables which "
f"are PEP-647 TypeGuards (got {return_type!r}). "
"Consider using an explicit strategy, or opening an issue."
)
return st.functions(
like=(lambda *a, **k: None) if args_types else (lambda: None),
returns=st.from_type(return_type),
)
@register(typing.TypeVar)
def resolve_TypeVar(thing):
type_var_key = f"typevar={thing!r}"
if getattr(thing, "__bound__", None) is not None:
bound = thing.__bound__
if isinstance(bound, typing.ForwardRef):
bound = _try_import_forward_ref(thing, bound)
strat = unwrap_strategies(st.from_type(bound))
if not isinstance(strat, OneOfStrategy):
return strat
# The bound was a union, or we resolved it as a union of subtypes,
# so we need to unpack the strategy to ensure consistency across uses.
# This incantation runs a sampled_from over the strategies inferred for
# each part of the union, wraps that in shared so that we only generate
# from one type per testcase, and flatmaps that back to instances.
return st.shared(
st.sampled_from(strat.original_strategies), key=type_var_key
).flatmap(lambda s: s)
builtin_scalar_types = [type(None), bool, int, float, str, bytes]
return st.shared(
st.sampled_from(
# Constraints may be None or () on various Python versions.
getattr(thing, "__constraints__", None)
or builtin_scalar_types,
),
key=type_var_key,
).flatmap(st.from_type)