Repository: beartype/beartype

Issue #261: [Feature Request] Official FreeBSD support
Created: 2023-07-22T18:10:06Z
=================================== FAILURES ===================================
_____________________________ test_coerce_hint_any _____________________________

    def test_coerce_hint_any() -> None:
        '''
        Test the private
        :func:`beartype._check.convert.convcoerce.coerce_hint_any` coercer.
        '''
    
        # ..................{ IMPORTS                            }..................
        # Defer test-specific imports.
        from beartype._check.convert.convcoerce import coerce_hint_any
        from beartype._util.py.utilpyversion import (
            IS_PYTHON_AT_LEAST_3_10,
            IS_PYTHON_AT_LEAST_3_9,
        )
    
        # ..................{ CORE                               }..................
        # Assert this coercer preserves an isinstanceable type as is.
        assert coerce_hint_any(int) is int
    
        # ..................{ PEP 585                            }..................
        # If the active Python interpreter targets Python >= 3.9 and thus supports
        # PEP 585...
        if IS_PYTHON_AT_LEAST_3_9:
            # Arbitrary PEP 585-compliant type hint.
            hint_pep585 = list[int]
   
            # Assert this coercer preserves the first passed instance of a PEP
            # 585-compliant type hint as is.
>           assert coerce_hint_any(hint_pep585) is hint_pep585
E           assert list[int] is list[int]
E            +  where list[int] = <function coerce_hint_any at 0x9712688b0>(list[int])

beartype_test/a00_unit/a60_check/a20_conv/test_convcoerce.py:92: AssertionError
=========================== short test summary info ============================
SKIPPED [1] beartype_test/a90_func/z90_lib/a90_nuitka/test_nuitka.py:31: Command "gcc" not found.
SKIPPED [1] beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep647.py:21: Python 3.9.17 < 3.10.0.
SKIPPED [1] beartype_test/a00_unit/a70_decor/a40_code/a00_mod/test_decor_contextlib.py:21: Python 3.9.17 < 3.11.0.
SKIPPED [1] beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep673.py:21: Python 3.9.17 < 3.11.0.
SKIPPED [1] beartype_test/a00_unit/a70_decor/a40_code/a80_nonpep/mod/test_codepandera.py:22: could not import 'pandera.__init__': No module named 'pandera'
SKIPPED [1] beartype_test/a90_func/pep/test_pep561_static.py:169: Command "pyright" not found.
================== 1 failed, 288 passed, 6 skipped in 31.12s ===================
*** Error code 1



Version: 0.15.0
Python-3.9
FreeBSD 13.2

Duplicate issue #498: The test test_is_hint_pep593_beartype fails
Created: 2025-02-22T08:58:54Z
_______________________________________________________________________________________________________________________________ test_is_hint_pep593_beartype ________________________________________________________________________________________________________________________________

func = <function test_is_hint_pep593_beartype.<locals>.<lambda> at 0xd46f6d7b420>, warning_cls = <class 'beartype.roar.BeartypeValeLambdaWarning'>

    def get_func_code_or_none(
        # Mandatory parameters.
        func: Callable,
    
        # Optional parameters.
        warning_cls: TypeWarning = _BeartypeUtilCallableWarning,
    ) -> Optional[str]:
        '''
        **Callable source code** (i.e., substring of all lines of the on-disk Python
        script or module declaring the passed pure-Python callable) if that callable
        was declared on-disk *or* :data:`None` otherwise (i.e., if that callable was
        dynamically declared in-memory).
    
        Specifically, this getter returns:
    
        * If the passed callable is a lambda function, the exact substring of that
          code declaring that lambda function.
        * Else, the concatenation of all lines of that code declaring that callable.
    
        Caveats
        -------
        **This getter is excruciatingly slow.** This getter should *only* be called
        when unavoidable and ideally *only* in performance-agnostic code paths.
        Notably, this getter finds relevant lines by parsing the script or module
        declaring the passed callable starting at the first line of that declaration
        and continuing until a rudimentary tokenizer implemented in pure-Python
        (with *no* concern for optimization and thus slow beyond all understanding
        of slow) detects the last line of that declaration. In theory, we could
        significantly optimize that routine; in practice, anyone who cares will
        preferably compile or JIT :mod:`beartype` instead.
    
        Parameters
        ----------
        func : Callable
            Callable to be inspected.
        warning_cls : TypeWarning, optional
            Type of warning to be emitted in the event of a non-fatal error.
            Defaults to :exc:`._BeartypeUtilCallableWarning`.
    
        Returns
        -------
        Optional[str]
            Either:
    
            * If the passed callable was physically declared by a file, the exact
              substring of all lines of that file declaring that callable.
            * If the passed callable was dynamically declared in-memory,
              :data:`None`.
    
        Warns
        -----
        warning_cls
            If the passed callable is a pure-Python lambda function that was
            physically declared by either:
    
            * A large file exceeding a sane maximum file size (e.g., 1MB). Note
              that:
    
              * If this is *not* explicitly guarded against, passing absurdly long
                strings to the :func:`ast.parse` function can actually induce a
                segmentation fault in the active Python process. This is a
                `longstanding and unresolved issue <ast issue_>`__ in the :mod:`ast`
                module.
              * Generously assuming each line of that file contains between
                40 to 80 characters, this maximum supports files of between 12,500
                to 25,000 lines, which at even the lower end of that range covers
                most real-world files of interest.
    
            * A complex (but *not* necessarily large) file causing the recursive
              :func:`ast.parse` or :func:`ast.unparse` function or any other
              :mod:`ast` callable to exceed Python's recursion limit by exhausting
              the stack.
            * A line of source code declaring two or more lambda functions (e.g.,
              ``lambdas = lambda: 'and black,', lambda: 'and pale,'``). In this
              case, the substring of code declaring the first such function is
              ambiguously returned; all subsequent such functions are unavoidably
              ignored.
    
        .. _ast issue:
           https://bugs.python.org/issue32758.
    
        See Also
        --------
        https://stackoverflow.com/questions/59498679/how-can-i-get-exactly-the-code-of-a-lambda-function-in-python/64421174#64421174
            StackOverflow answer strongly inspiring this implementation.
        '''
    
        # Avoid circular import dependencies.
        from beartype._util.func.utilfunctest import is_func_lambda
        from beartype._util.text.utiltextlabel import label_callable
    
        # If the passed callable is a pure-Python lambda function...
        if is_func_lambda(func):
            # Attempt to parse the substring of the source code defining this lambda
            # from the file providing that code.
            #
            # For safety, this function reduces *ALL* exceptions raised by this
            # introspection to non-fatal warnings and returns "None". Why? Because
            # the standard "ast" module in general and our "_LambdaNodeUnparser"
            # class in specific are sufficiently fragile as to warrant extreme
            # caution. AST parsing and unparsing is notoriously unreliable across
            # different versions of different Python interpreters and compilers.
            #
            # Moreover, we *NEVER* call this function in a critical code path; we
            # *ONLY* call this function to construct human-readable exception
            # messages. Clearly, raising low-level non-human-readable exceptions
            # when attempting to raise high-level human-readable exceptions rather
            # defeats the entire purpose of the latter.
            #
            # Lastly, note that "pytest" will still fail any tests emitting
            # unexpected warnings. In short, raising exceptions here would gain
            # @beartype little and cost @beartype much.
            try:
                # String concatenating all lines of the file defining that lambda if
                # that lambda is defined by a file *OR* "None" otherwise.
                lambda_file_code = get_func_file_code_lines_or_none(
                    func=func, warning_cls=warning_cls)
    
                # If that lambda is defined by a file...
                if lambda_file_code:
                    # Code object underlying this lambda.
                    func_codeobj = get_func_codeobj(func)
    
                    # If this file exceeds a sane maximum file size, emit a
                    # non-fatal warning and safely ignore this file.
                    if len(lambda_file_code) >= _LAMBDA_CODE_FILESIZE_MAX:
                        issue_warning(
                            cls=warning_cls,
                            message=(
                                f'{label_callable(func)} not parsable, '
                                f'as file size exceeds safe maximum '
                                f'{_LAMBDA_CODE_FILESIZE_MAX}MB.'
                            ),
                        )
                    # Else, this file *SHOULD* be safely parsable by the standard
                    # "ast" module without inducing a fatal segmentation fault.
                    else:
                        # Abstract syntax tree (AST) parsed from this file.
                        ast_tree = ast_parse(lambda_file_code)
    
                        # Lambda node unparser decompiling all AST lambda nodes
                        # encapsulating lambda functions starting at the same line
                        # number as the passed lambda in this file.
                        lambda_node_unparser = _LambdaNodeUnparser(
                            lambda_lineno=func_codeobj.co_firstlineno)
    
                        # Perform this decompilation.
                        lambda_node_unparser.visit(ast_tree)
    
                        # List of each code substring exactly covering each lambda
                        # function starting at that line number.
                        lambdas_code = lambda_node_unparser.lambdas_code
    
                        # If one or more lambda functions start at that line
                        # number...
                        if lambdas_code:
                            # If two or more lambda functions start at that line
                            # number, emit a non-fatal warning. Since lambda
                            # functions only provide a starting line number rather
                            # than both starting line number *AND* column, we have
                            # *NO* means of disambiguating between these lambda
                            # functions and thus *CANNOT* raise an exception.
                            if len(lambdas_code) >= 2:
                                # Human-readable concatenation of the definitions of
                                # all lambda functions defined on that line.
                                lambdas_code_str = '\n    '.join(lambdas_code)
    
                                # Emit this warning.
                                issue_warning(
                                    cls=warning_cls,
                                    message=(
                                        f'{label_callable(func)} ambiguous, '
                                        f'as that line defines '
                                        f'{len(lambdas_code)} lambdas; '
                                        f'arbitrarily selecting first '
                                        f'lambda:\n{lambdas_code_str}'
                                    ),
                                )
                            # Else, that line number defines one lambda.
    
                            # Return the substring covering that lambda.
                            return lambdas_code[0]
                        # Else, *NO* lambda functions start at that line number. In
                        # this case, emit a non-fatal warning.
                        #
                        # Ideally, we would instead raise a fatal exception. Why?
                        # Because this edge case violates expectations. Since the
                        # passed lambda function claims it originates from some line
                        # number of some file *AND* since that file both exists and
                        # is parsable as valid Python, we expect that line number to
                        # define one or more lambda functions. If it does not,
                        # raising an exception seems superficially reasonable. Yet,
                        # we don't. See above.
                        else:
>                           issue_warning(
                                cls=warning_cls,
                                message=f'{label_callable(func)} not found.',
                            )

beartype/_util/func/utilfunccode.py:453: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'beartype.roar.BeartypeValeLambdaWarning'>
message = 'lambda function accepting 1 argument(s) beartype_test.a00_unit.a20_util.hint.a00_pep.proposal.test_pep593.test_is_hin...pe/work-py311/beartype-0.20.0/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_pep593.py" line 74 not found.'

    def issue_warning(cls: TypeWarning, message: str) -> None:
        # Time to cry your tears! Now cry!
>       warn(message, cls)
E       beartype.roar.BeartypeValeLambdaWarning: lambda function accepting 1 argument(s) beartype_test.a00_unit.a20_util.hint.a00_pep.proposal.test_pep593.test_is_hint_pep593_beartype.<lambda>() in file "/usr/ports/devel/py-beartype/work-py311/beartype-0.20.0/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_pep593.py" line 74 not found.

beartype/_util/error/utilerrwarn.py:105: BeartypeValeLambdaWarning

During handling of the above exception, another exception occurred:

    def test_is_hint_pep593_beartype() -> None:
        '''
        Test usage of the private
        :mod:`beartype._util.hint.pep.proposal.pep593.is_hint_pep593_beartype`
        tester.
        '''
    
        # ....................{ IMPORTS                        }....................
        # Defer test-specific imports.
        from beartype.roar import BeartypeDecorHintPepException
        from beartype.typing import Annotated
        from beartype.vale import Is
        from beartype._util.hint.pep.proposal.pep593 import is_hint_pep593_beartype
        from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10
        from pytest import raises
    
        # ....................{ PASS                           }....................
        # Assert this tester rejects beartype-agnostic metahints.
        assert is_hint_pep593_beartype(
            Annotated[str, 'And may there be no sadness of farewell']) is False
    
        # If the active Python interpreter targets Python >= 3.10, assert this
        # tester accepts a beartype-specific metahint annotated by a beartype
        # validator defined as a lambda function. For unknown reasons, the obsolete
        # Python 3.9-specific implementation of the standard "inspect" module fails
        # to inspect the definition of this lambda function; this then induces
        # unexpected warnings. Although we *COULD* catch these warnings here, it's
        # simpler to simply ignore Python 3.9 for now. Yo! Laziness prevails.
        if IS_PYTHON_AT_LEAST_3_10:
>           assert is_hint_pep593_beartype(
                Annotated[str, Is[lambda text: bool(text)]]) is True

beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_pep593.py:74: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
beartype/_util/hint/pep/proposal/pep593.py:120: in is_hint_pep593_beartype
    die_unless_hint_pep593(hint)
beartype/_util/hint/pep/proposal/pep593.py:55: in die_unless_hint_pep593
    if not is_hint_pep593(hint):
beartype/_util/hint/pep/proposal/pep593.py:88: in is_hint_pep593
    return get_hint_pep_sign_or_none(hint) is HintSignAnnotated
beartype/_util/cache/utilcachecall.py:249: in _callable_cached
    raise exception
beartype/_util/cache/utilcachecall.py:241: in _callable_cached
    return_value = args_flat_to_return_value[args_flat] = func(
beartype/_util/hint/pep/utilpepget.py:576: in get_hint_pep_sign_or_none
    hint_repr = get_hint_repr(hint)
beartype/_util/cache/utilcachecall.py:249: in _callable_cached
    raise exception
beartype/_util/cache/utilcachecall.py:241: in _callable_cached
    return_value = args_flat_to_return_value[args_flat] = func(
beartype/_util/hint/utilhintget.py:57: in get_hint_repr
    return repr(hint)
/usr/local/lib/python3.11/typing.py:2162: in __repr__
    ", ".join(repr(a) for a in self.__metadata__)
/usr/local/lib/python3.11/typing.py:2162: in <genexpr>
    ", ".join(repr(a) for a in self.__metadata__)
beartype/vale/_core/_valecore.py:322: in __repr__
    self._get_repr = self._get_repr()
beartype/vale/_is/_valeis.py:317: in <lambda>
    f'{represent_func(func=is_valid, warning_cls=BeartypeValeLambdaWarning)}'
beartype/_util/text/utiltextrepr.py:217: in represent_func
    get_func_code_or_none(func=func, warning_cls=warning_cls) or
beartype/_util/func/utilfunccode.py:469: in get_func_code_or_none
    issue_warning(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'beartype.roar.BeartypeValeLambdaWarning'>
message = 'lambda function accepting 1 argument(s) beartype_test.a00_unit.a20_util.hint.a00_pep.proposal.test_pep593.test_is_hin.../work-py311/beartype-0.20.0/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_pep593.py" line 74 not found.\n'

    def issue_warning(cls: TypeWarning, message: str) -> None:
        # Time to cry your tears! Now cry!
>       warn(message, cls)
E       beartype.roar.BeartypeValeLambdaWarning: lambda function accepting 1 argument(s) beartype_test.a00_unit.a20_util.hint.a00_pep.proposal.test_pep593.test_is_hint_pep593_beartype.<lambda>() in file "/usr/ports/devel/py-beartype/work-py311/beartype-0.20.0/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_pep593.py" line 74 not parsable:
E       Traceback (most recent call last):
E         File "/usr/ports/devel/py-beartype/work-py311/beartype-0.20.0/beartype/_util/func/utilfunccode.py", line 453, in get_func_code_or_none
E           issue_warning(
E         File "/usr/ports/devel/py-beartype/work-py311/beartype-0.20.0/beartype/_util/error/utilerrwarn.py", line 105, in issue_warning
E           warn(message, cls)
E       beartype.roar.BeartypeValeLambdaWarning: lambda function accepting 1 argument(s) beartype_test.a00_unit.a20_util.hint.a00_pep.proposal.test_pep593.test_is_hint_pep593_beartype.<lambda>() in file "/usr/ports/devel/py-beartype/work-py311/beartype-0.20.0/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_pep593.py" line 74 not found.

beartype/_util/error/utilerrwarn.py:105: BeartypeValeLambdaWarning

Version: 0.20.0
Python-3.11
FreeBSD 14.2

Closing comment (2025-02-22T17:41:05Z): lolbro. Good Ol' FreeBSD, eh? In theory, @beartype continues to love us some *BSD. In practice, @beartype still lacks official *BSD support. This issue is thus a duplicate of #261, which has been around since before the dawn of Guido.
Please, somebody. Submit a PR improving *BSD support. Until then, emoji cat regretfully closes this duplicate issue. 😿