#!/usr/bin/env python3 """One interpreter start for the whole of 99-python3-beartype. Emits `key=value' lines, one per line, in file order; the suite reads them with grep. A python3 start on this arm is tens of seconds, and every case here is a decorator applied to a function of two parameters and then called on a value under twenty bytes -- microseconds beside the start -- so taking the start ONCE is the difference between a half-minute suite and a twenty-minute one. THREE RULES THE VALUES OBEY, ALL ABOUT WHAT SURVIVES THE TRANSPORT. The runner parses its input line by line, so no value may contain a NEWLINE. The suite splits each line on the FIRST `=', so a VALUE containing `=' is safe and a KEY containing one is not. Every key emitted here is `[a-z0-9_]+'. NO RAW VIOLATION MESSAGE IS EVER EMITTED. Every one of them embeds the repr of the offending object, and for anything but a scalar that repr carries a HEAP ADDRESS -- `<__main__.Bad object at 0x7f...>'. A case asserting such a string would be asserting the address, and would red on every second run for a reason that has nothing to do with beartype. What the cases below assert instead are DERIVED FACTS about each message: that it names the parameter, that it names the hint, that it says which type was found. `msg_*_has_*' is that shape throughout. AND NOTHING HERE DEPENDS ON THE STDOUT ENCODING. This image's guest runs under the C locale; every fixture and every expected value is pure ASCII. ORDER IS LOAD-BEARING IN ONE PLACE. The class-decoration section reads `E.bare is _bare_before' -- the identity of a method captured BEFORE the class was decorated -- so the capture must precede the decoration and nothing may re-decorate `E' afterwards. WHY THE RANDOM-SAMPLING SECTION LOOPS. beartype's default strategy is O(1): for a `list[int]' it checks ONE pseudo-randomly chosen item per call, not all of them. A list with a single bad item therefore raises only SOMETIMES -- measured at 74 of 300 calls on a 3-item list, which is exactly 1/3 -- so no case here may pass a partially-bad container and assert a violation. Every deep-check case below passes a container in which EVERY item is bad, which raises on any sample the strategy could take, and `list_allbad_trials' asserts that over 50 calls it raises 50 times. The index the message names is likewise never asserted: it is uniform over the list and changes call to call. """ import sys emitted = [] def emit(key, value): emitted.append(key) print('%s=%s' % (key, value)) try: import beartype as bt_mod from beartype import beartype except Exception as exc: # pragma: no cover print('import_error=%s: %s' % (type(exc).__name__, exc)) sys.exit(1) import typing # ---------------------------------------------------------------- identity --- # WHICH COPY ANSWERED. Every behavioural case below is a statement about the # package named here; without this one they would be statements about whatever # was first on sys.path. emit('module_file', getattr(bt_mod, '__file__', '')) emit('is_package', 'yes' if hasattr(bt_mod, '__path__') else 'no') emit('version_attr', getattr(bt_mod, '__version__', '')) # `beartype.meta' is the project's own single source of truth for its version -- # `__init__.py' reads `__version__' back OUT of it -- so the two agreeing is a # statement about the install rather than a tautology only if both are read. try: from beartype import meta as _meta emit('meta_version', getattr(_meta, 'VERSION', '')) emit('meta_name', getattr(_meta, 'NAME', '')) except Exception as exc: emit('meta_version', '<%s>' % type(exc).__name__) emit('meta_name', '<%s>' % type(exc).__name__) try: from importlib.metadata import version as _dist_version emit('dist_version', _dist_version('beartype')) except Exception as exc: emit('dist_version', '<%s>' % type(exc).__name__) # THE PUBLIC SURFACE, as a sorted comma-joined list. beartype exports FOUR # configuration names beside the decorator itself, and a release that dropped one # would still pass every behavioural case below because none of the four is # needed to decorate anything. emit('public_names', ','.join(sorted( n for n in dir(bt_mod) if not n.startswith('_')))) # The three subpackages a caller reaches for by name. `import beartype' # succeeding says the top-level `__init__' ran; it does not say these resolve. for _sub in ('roar', 'typing', 'door', 'vale', 'cave', 'claw'): try: __import__('beartype.' + _sub) emit('submodule_' + _sub, 'yes') except Exception as exc: emit('submodule_' + _sub, '<%s>' % type(exc).__name__) # ------------------------------------------------------------ the exceptions -- # The two violation classes ARE the contract: a caller catches one or the other # to tell a bad argument from a bad return. Their common base is what a caller # who wants both catches, so the relationship is asserted rather than inferred # from the names. from beartype.roar import ( BeartypeCallHintParamViolation as ParamViolation, BeartypeCallHintReturnViolation as ReturnViolation, BeartypeCallHintViolation as AnyViolation, BeartypeCallHintForwardRefException as ForwardRefException, BeartypeException, ) emit('exc_param_name', ParamViolation.__name__) emit('exc_return_name', ReturnViolation.__name__) emit('exc_param_is_violation', 'yes' if issubclass(ParamViolation, AnyViolation) else 'no') emit('exc_return_is_violation', 'yes' if issubclass(ReturnViolation, AnyViolation) else 'no') emit('exc_param_is_beartype', 'yes' if issubclass(ParamViolation, BeartypeException) else 'no') # The two are SIBLINGS, not one a subclass of the other. A caller catching # ParamViolation must not silently also catch a return violation, and this is the # case that would notice if a release collapsed them. emit('exc_param_not_return', 'yes' if not issubclass(ParamViolation, ReturnViolation) else 'no') emit('exc_return_not_param', 'yes' if not issubclass(ReturnViolation, ParamViolation) else 'no') # ...and beartype's own exceptions are ordinary Python exceptions, so `except # Exception' around a decorated call still works. emit('exc_is_exception', 'yes' if issubclass(BeartypeException, Exception) else 'no') # ------------------------------------------------------------------ helpers --- def outcome(fn, *args): """`ok' if the call returned, `param'/`return' for the two violations, else the exception class name. Every behavioural case below is one of these.""" try: fn(*args) return 'ok' except ParamViolation: return 'param' except ReturnViolation: return 'return' except Exception as exc: return type(exc).__name__ def message(fn, *args): """The violation message, or `'. Only ever consumed by the `msg_*' cases below, which emit facts ABOUT it and never it.""" try: fn(*args) return '' except AnyViolation as exc: return str(exc) except Exception as exc: return '<%s>' % type(exc).__name__ def scalar_probe(hint): """A one-parameter function annotated with `hint', for the matrix below.""" @beartype def _f(v: hint) -> str: return 'ok' return _f # ================================================== THE CONTRACT, BOTH HALVES == # A call that SATISFIES the annotations returns normally and returns the value # unchanged; a call that violates one raises. Everything else in this probe is a # variation on these two lines. @beartype def contract(x: int, s: str) -> int: return x + len(s) emit('contract_ok', contract(3, 'abc')) emit('contract_bad_param', outcome(contract, '3', 'abc')) emit('contract_bad_param_second', outcome(contract, 3, 42)) emit('contract_ok_repeat', contract(3, 'abc')) # no state carried over # THE RETURN VALUE IS THE CALLEE'S OWN OBJECT, not a copy and not a proxy. # beartype checks the return and hands it back; a decorator that rebuilt it would # pass every type assertion above and break every caller relying on identity. _sentinel = object() @beartype def passthrough(o: object) -> object: return o emit('return_identity', 'yes' if passthrough(_sentinel) is _sentinel else 'no') @beartype def passthrough_list(xs: list[int]) -> list[int]: return xs _L = [1, 2, 3] emit('return_identity_list', 'yes' if passthrough_list(_L) is _L else 'no') # THE RETURN HALF OF THE CONTRACT. The body is deliberately WRONG -- it returns # the int it was given from a function annotated `-> str' -- so the only thing # that can raise is the return check. @beartype def bad_return(x: int) -> str: return x emit('bad_return_outcome', outcome(bad_return, 1)) # ------------------------------------------- the messages name the offender --- # THE TICKET'S OWN REQUIREMENT: the message must name the offending parameter and # the type it failed. Asserted as facts about the string rather than as the # string, for the reason the module docstring gives. _pmsg = message(contract, '3', 'abc') emit('msg_param_names_param', 'yes' if 'parameter x' in _pmsg else 'no') emit('msg_param_names_hint', 'yes' if "" in _pmsg else 'no') emit('msg_param_names_found', 'yes' if "str '3'" in _pmsg else 'no') emit('msg_param_names_func', 'yes' if 'contract' in _pmsg else 'no') emit('msg_param_says_violates', 'yes' if 'violates type hint' in _pmsg else 'no') # The SECOND parameter, so that a message which always named the first would fail # here. This is the case that makes `msg_param_names_param' mean something. _p2msg = message(contract, 3, 42) emit('msg_param2_names_param', 'yes' if 'parameter s' in _p2msg else 'no') emit('msg_param2_not_first', 'yes' if 'parameter x' not in _p2msg else 'no') _rmsg = message(bad_return, 1) emit('msg_return_says_return', 'yes' if 'return' in _rmsg else 'no') emit('msg_return_names_hint', 'yes' if "" in _rmsg else 'no') # ...and it does NOT call the failure a parameter, which is the whole of how a # reader tells the two halves apart in a log. emit('msg_return_not_param', 'yes' if 'parameter' not in _rmsg else 'no') # Single-line, both of them. A multi-line violation message would break every # log the platform keeps, and it is cheap to notice here. emit('msg_param_lines', str(len(_pmsg.splitlines()))) emit('msg_return_lines', str(len(_rmsg.splitlines()))) # ======================================================== NO ANNOTATIONS AT ALL # THE STRONGEST STATEMENT IN THIS SUITE, and the ticket asks for it in those # words -- "no added cost". For a function with no annotations beartype returns # THE FUNCTION ITSELF: not a wrapper that checks nothing, the identical object. # There is therefore no call-time cost to measure, because there is no call-time # anything. def _unannotated(a, b): return (a, b) emit('unannotated_identity', 'yes' if beartype(_unannotated) is _unannotated else 'no') emit('unannotated_call', str(beartype(_unannotated)(1, 'x'))) # ...whereas an ANNOTATED function does get a wrapper, which is the contrast that # makes the line above a finding rather than an accident. `__wrapped__' points # back at the original, so `inspect' still sees the real signature. def _annotated(a: int) -> int: return a _wrapped = beartype(_annotated) emit('annotated_not_identity', 'yes' if _wrapped is not _annotated else 'no') emit('annotated_has_wrapped', 'yes' if getattr(_wrapped, '__wrapped__', None) is _annotated else 'no') emit('annotated_name', _wrapped.__name__) # The wrapper is transparent to introspection: same name, same annotations, same # signature. A caller that reads annotations at runtime -- which is most of the # reason to have them -- must see no difference. @beartype def introspect(a: int, b: str = 'x') -> bool: return True emit('introspect_annotations', ','.join('%s:%s' % (k, getattr(v, '__name__', v)) for k, v in introspect.__annotations__.items())) try: import inspect as _inspect emit('introspect_signature', str(_inspect.signature(introspect))) except Exception as exc: emit('introspect_signature', '<%s>' % type(exc).__name__) # =============================================== BUILTIN SCALARS, AS A MATRIX == # Each row is one annotation against seven values, rendered `value:outcome'. A # matrix rather than a case apiece because the interesting content is in the # DISAGREEMENTS between rows, and those are only visible side by side. # # TWO ROWS CARRY FINDINGS a reader would otherwise take for port bugs. # # `bool' SATISFIES `int'. Python's bool IS a subclass of int, beartype checks # with isinstance(), and so `True' passes an `int' annotation. That is Python, # not beartype and not this platform. # # `int' DOES NOT SATISFY `float'. PEP 484 asks a checker to accept an int # where a float is annotated -- the "implicit numeric tower" -- and beartype # 0.17.2 does NOT implement it by default. A caller who annotated `-> float' # and returned `0' gets a violation. Upstream's behaviour at this version. _scalar_values = ( ('int1', 1), ('booltrue', True), ('float1', 1.0), ('strx', 'x'), ('bytesx', b'x'), ('complex1', 1 + 0j), ('none', None), ) for _hname, _hint in (('int', int), ('float', float), ('str', str), ('bytes', bytes), ('bool', bool), ('complex', complex)): _f = scalar_probe(_hint) emit('scalar_' + _hname, ','.join('%s:%s' % (_vn, outcome(_f, _v)) for _vn, _v in _scalar_values)) # ================================================ CONTAINERS: DEEP vs SHALLOW == # THE SINGLE MOST MISREADABLE THING ABOUT THIS PACKAGE AT THIS VERSION. # # beartype 0.17.2 DEEPLY checks `list[...]' and `tuple[...]' -- it looks inside # at the items. It does NOT deeply check `dict[...]', `set[...]' or # `frozenset[...]': for those the annotation degrades to a plain isinstance() # against the container type and the parameters are IGNORED. So # `dict[str, int]' accepts `{1: 'x'}' without complaint. # # That is upstream 0.17.2 and not a gap in this port. It is asserted here in # both directions -- the deep containers raising and the shallow ones NOT # raising -- so that a future beartype which deepened dicts reds HERE, is looked # at, and updates the suite, rather than silently changing what it covers. # # `beartype.door.is_bearable' is asked the same question directly underneath, # because it reaches the same checker without a decorator in the way: if the two # ever disagreed the fault would be in the decorator, and only asking both can # say so. @beartype def take_list(xs: list[int]) -> str: return 'ok' @beartype def take_dict(d: dict[str, int]) -> str: return 'ok' @beartype def take_tuple(t: tuple[int, str]) -> str: return 'ok' @beartype def take_tuple_var(t: tuple[int, ...]) -> str: return 'ok' @beartype def take_set(s: set[int]) -> str: return 'ok' @beartype def take_frozenset(s: frozenset[int]) -> str: return 'ok' # DEEP -- list. Every item bad, so any sample the O(1) strategy takes finds one. emit('list_ok', outcome(take_list, [1, 2, 3])) emit('list_empty', outcome(take_list, [])) emit('list_allbad', outcome(take_list, ['a', 'b', 'c'])) emit('list_wrong_container', outcome(take_list, (1, 2))) emit('list_not_container', outcome(take_list, 1)) # NESTED, so that the deep check is shown to recurse rather than to stop one # level down. emit('list_nested_ok', outcome(scalar_probe(list[list[int]]), [[1], [2]])) emit('list_nested_allbad', outcome(scalar_probe(list[list[int]]), [['a'], ['b']])) # THE O(1) STRATEGY, MEASURED. 50 calls with every item bad must raise 50 times; # a strategy that sampled and got it wrong even once would show up as a count # below 50. The complementary half -- that a PARTIALLY bad list raises only # sometimes -- is deliberately NOT asserted as a number: it is a random variable, # and a suite that pinned it would red on its own tail. What is asserted is that # it is not always-raise and not never-raise, over 60 trials, which is the fact a # reader needs and which has a vanishing chance of being wrong. _allbad_raises = 0 for _ in range(50): if outcome(take_list, ['a', 'b', 'c']) == 'param': _allbad_raises += 1 emit('list_allbad_trials', str(_allbad_raises)) _partial_raises = 0 for _ in range(60): if outcome(take_list, [1, 'a', 3]) == 'param': _partial_raises += 1 emit('list_partial_sampled', 'yes' if 0 < _partial_raises < 60 else 'no:%d' % _partial_raises) # SHALLOW -- dict. `{1: 'x'}' violates BOTH parameters and is accepted anyway. emit('dict_ok', outcome(take_dict, {'a': 1})) emit('dict_bad_value', outcome(take_dict, {'a': 'x'})) emit('dict_bad_key', outcome(take_dict, {1: 1})) emit('dict_bad_both', outcome(take_dict, {1: 'x'})) emit('dict_wrong_container', outcome(take_dict, [1])) # SHALLOW -- set and frozenset, the same finding in two more places. emit('set_ok', outcome(take_set, {1, 2})) emit('set_bad', outcome(take_set, {'a'})) emit('frozenset_bad', outcome(take_frozenset, frozenset({'a'}))) # DEEP -- tuple, both the fixed-length and the variadic form. The fixed form # checks the LENGTH too, which no other container here does. emit('tuple_ok', outcome(take_tuple, (1, 'a'))) emit('tuple_bad', outcome(take_tuple, ('a', 1))) emit('tuple_short', outcome(take_tuple, (1,))) emit('tuple_var_ok', outcome(take_tuple_var, (1, 2, 3))) emit('tuple_var_allbad', outcome(take_tuple_var, ('a', 'b'))) # THE SAME QUESTIONS PUT TO `beartype.door.is_bearable', which reaches the # checker with no decorator in between. The two must agree, and the dict answer # being True here is what proves the shallowness is the CHECKER's and not the # decorator's. try: from beartype.door import is_bearable emit('door_list_bad', str(is_bearable(['a'], list[int]))) emit('door_list_ok', str(is_bearable([1], list[int]))) emit('door_dict_bad', str(is_bearable({'a': 'b'}, dict[str, int]))) emit('door_set_bad', str(is_bearable({'a'}, set[int]))) emit('door_tuple_bad', str(is_bearable(('a', 1), tuple[int, str]))) except Exception as exc: for _k in ('door_list_bad', 'door_list_ok', 'door_dict_bad', 'door_set_bad', 'door_tuple_bad'): emit(_k, '<%s>' % type(exc).__name__) # ============================================= UNION, OPTIONAL, ANY, NoneType == # `typing.Union[int, str]' and `int | str' are two spellings of one hint and must # behave identically -- the PEP 604 form is the one a reader will actually write, # and it is a DIFFERENT object (`types.UnionType', not `typing.Union'), reduced # by a different code path inside beartype. Asserting both is what says the two # paths agree. # # `Optional[int]' IS `Union[int, None]', which is why it accepts None and rejects # str; the identity is asserted directly beneath so the reader is not asked to # take it on trust. _union_values = (('int1', 1), ('strx', 'x'), ('float1', 1.0), ('none', None), ('list1', [1])) for _hname, _hint in (('union', typing.Union[int, str]), ('pipe', int | str), ('optional', typing.Optional[int]), ('pipe_none', int | None), ('any', typing.Any), ('object', object), ('nonetype', type(None))): _f = scalar_probe(_hint) emit('hint_' + _hname, ','.join('%s:%s' % (_vn, outcome(_f, _v)) for _vn, _v in _union_values)) emit('optional_is_union', 'yes' if typing.Optional[int] == typing.Union[int, None] else 'no') emit('pipe_type', type(int | str).__name__) emit('pipe_repr', repr(int | str)) # `Any' accepts everything INCLUDING None, and `object' does too. The pair is # here because a checker that silently treated `Any' as `object' would be # indistinguishable on these values -- which is the point: at runtime they are # the same permission, and a reader who expected `Any' to be special is corrected # here. # A DEFAULT ARGUMENT is checked against the annotation like any other value, and # `Optional[int] = None' is the one shape where the default would fail the # annotation if `Optional' were read as "int, may be omitted". @beartype def with_default(v: typing.Optional[int] = None) -> str: return str(v) emit('default_omitted', with_default()) emit('default_given', with_default(5)) emit('default_bad', outcome(with_default, 'x')) # ================================================================== PROTOCOL == # A `runtime_checkable' Protocol is checked STRUCTURALLY: what is asserted is that # the argument has a `close' method, not what class it came from. `Good' and # `AlsoGood' share no base and both satisfy it, which is the whole point and is # what a nominal check would fail. # # The structural check is SHALLOW BY DESIGN -- typing's own `__instancecheck__' # looks for the ATTRIBUTE and never at its signature -- so `WrongSignature', # whose `close' takes three arguments, PASSES. That is CPython's Protocol, not # beartype's doing, and it is asserted rather than left out so it is not read as # a port defect. # # And a Protocol WITHOUT `@runtime_checkable' cannot be used at all: beartype # raises at DECORATION time, before any call, with its own # BeartypeDecorHintPep3119Exception. Raising early is the right behaviour -- # the alternative is a TypeError from isinstance() at every call site -- and the # case below records that it is a decoration-time failure by never calling the # function. @typing.runtime_checkable class Closer(typing.Protocol): def close(self) -> None: ... class Good: def close(self) -> None: pass class AlsoGood: # no relation to Good whatsoever def close(self) -> None: pass class WrongSignature: def close(self, a, b) -> None: pass class NotACloser: pass @beartype def needs_closer(o: Closer) -> str: return 'ok' emit('protocol_good', outcome(needs_closer, Good())) emit('protocol_unrelated', outcome(needs_closer, AlsoGood())) emit('protocol_wrong_signature', outcome(needs_closer, WrongSignature())) emit('protocol_missing', outcome(needs_closer, NotACloser())) _prot_msg = message(needs_closer, NotACloser()) emit('msg_protocol_names_protocol', 'yes' if 'protocol' in _prot_msg else 'no') emit('msg_protocol_names_param', 'yes' if 'parameter o' in _prot_msg else 'no') class NotRuntimeCheckable(typing.Protocol): def close(self) -> None: ... try: @beartype def needs_nrc(o: NotRuntimeCheckable) -> str: return 'ok' emit('protocol_not_runtime', 'decorated') except Exception as exc: emit('protocol_not_runtime', type(exc).__name__) # ========================================================= FORWARD REFERENCES == # An annotation given AS A STRING is resolved lazily -- at the first call, not at # decoration -- which is what lets a function be annotated with a class defined # further down the file. `Later' below does not exist when `takes_later' is # decorated, and the decoration succeeds anyway. # # An UNRESOLVABLE reference is therefore a CALL-time failure and not a decoration # one, and it raises beartype's own BeartypeCallHintForwardRefException rather # than NameError. Both halves are asserted: a checker that resolved eagerly # would fail at the decoration below and never reach the call. @beartype def takes_later(o: 'Later') -> str: return 'ok' emit('forwardref_decorated', 'yes') class Later: pass emit('forwardref_ok', outcome(takes_later, Later())) emit('forwardref_bad', outcome(takes_later, Good())) _fwd_msg = message(takes_later, Good()) emit('msg_forwardref_names_class', 'yes' if 'Later' in _fwd_msg else 'no') @beartype def takes_missing(o: 'NeverDefinedAnywhere') -> str: return 'ok' emit('forwardref_missing_decorated', 'yes') try: takes_missing(1) emit('forwardref_missing_call', 'ok') except ForwardRefException as exc: emit('forwardref_missing_call', 'ForwardRefException') emit('forwardref_missing_names_it', 'yes' if 'NeverDefinedAnywhere' in str(exc) else 'no') except Exception as exc: emit('forwardref_missing_call', type(exc).__name__) emit('forwardref_missing_names_it', 'n/a') # ==================================================== @beartype ON A CLASS ==== # Applied to a CLASS, `@beartype' decorates the class's methods IN PLACE and # returns THE SAME CLASS OBJECT -- not a subclass and not a wrapper. That # identity is the first case below because everything a caller does with the # class afterwards depends on it: `isinstance', pickling by qualified name, and # any registry keyed on the class all break under a wrapper. # # All five method kinds are covered, because each reaches beartype by a different # route: a plain method is a function on the class, a staticmethod and a # classmethod are descriptors wrapping one, `__init__' returns None and so has a # return annotation nothing else has, and a property's getter is reached through # a third descriptor again. # # And an UNANNOTATED method inside a decorated class is left ALONE -- the same # identity rule as the module-level `unannotated_identity' above, applied one # level in. `_bare_before' is captured before the decoration for that case; see # the module docstring on ordering. class Counter: def __init__(self, n: int) -> None: self.n = n def add(self, k: int) -> int: return self.n + k @staticmethod def shout(s: str) -> str: return s.upper() @classmethod def doubled(cls, x: int) -> int: return x * 2 @property def twice(self) -> int: return self.n * 2 def bare(self, anything): return anything _bare_before = Counter.bare _Counter_before = Counter Counter = beartype(Counter) emit('class_identity', 'yes' if Counter is _Counter_before else 'no') emit('class_name', Counter.__name__) emit('class_bare_untouched', 'yes' if Counter.bare is _bare_before else 'no') emit('class_method_wrapped', 'yes' if hasattr(Counter.add, '__wrapped__') else 'no') _c = Counter(2) emit('class_init_ok', 'yes') emit('class_add_ok', str(_c.add(3))) emit('class_static_ok', Counter.shout('a')) emit('class_classmethod_ok', str(Counter.doubled(4))) emit('class_property_ok', str(_c.twice)) emit('class_bare_ok', 'yes' if _c.bare(_sentinel) is _sentinel else 'no') # EACH METHOD KIND ENFORCES. A decoration that reached only plain methods would # pass `class_add_bad' and fail the three below it. emit('class_init_bad', outcome(Counter, 'x')) emit('class_add_bad', outcome(_c.add, 'x')) emit('class_static_bad', outcome(Counter.shout, 1)) emit('class_classmethod_bad', outcome(Counter.doubled, 'x')) _cm_msg = message(_c.add, 'x') emit('msg_method_names_param', 'yes' if 'parameter k' in _cm_msg else 'no') emit('msg_method_names_class', 'yes' if 'Counter' in _cm_msg else 'no') # =================================================================== CONFIG ==== # The default strategy is O(1), which is the fact every deep-check case above # rests on, so it is read off the configuration rather than assumed from the # behaviour it produces. emit('conf_strategy', bt_mod.BeartypeConf().strategy.name) emit('conf_repr', repr(bt_mod.BeartypeConf())) emit('strategy_members', ','.join(s.name for s in bt_mod.BeartypeStrategy)) emit('verbosity_members', ','.join(v.name for v in bt_mod.BeartypeViolationVerbosity)) # `BeartypeStrategy.O0' turns checking OFF, and a conf carrying it must make the # decorator a no-op -- the same identity finding as the unannotated case, reached # through configuration instead. This is the escape hatch a caller reaches for # on a hot path, so it is worth knowing it really is free. try: _noop_conf = bt_mod.BeartypeConf(strategy=bt_mod.BeartypeStrategy.O0) def _o0(a: int) -> int: return a _o0_decorated = beartype(_o0, conf=_noop_conf) emit('strategy_o0_identity', 'yes' if _o0_decorated is _o0 else 'no') emit('strategy_o0_no_check', outcome(_o0_decorated, 'x')) except Exception as exc: emit('strategy_o0_identity', '<%s>' % type(exc).__name__) emit('strategy_o0_no_check', '<%s>' % type(exc).__name__) # ==================================================================== the end == emit('emitted_count', str(len(emitted) + 1)) print('probe_done=yes')