Skip to content

Another way to disable rewrites? #5473

Open

Description

@nedbat

I am trying to add a pytest plugin to coverage.py. Because the plugin is in the coverage package, coverage/init.py will be imported when the plugin is imported. Because I'm using coverage to run pytest in the first place, coverage/__init__.py has already been imported. This leads to a warning:

PytestAssertRewriteWarning: Module already imported so cannot be rewritten: coverage

I can disable this warning by adding PYTEST_DONT_REWRITE to the coverage/__init__.py docstring, but then that word is visible in the docstring for my package. Ugly.

Much of pytest is controlled by specially named attributes in modules. It would be great if I could control rewriting by adding an attribute to my module instead of amending the docstring.

If needed, I can provide a reproducer that shows the warning.

Activity

  1. added
    topic: rewriterelated to the assertion rewrite mechanism
    type: enhancementnew feature or API change, should be merged into features branch
    on Jun 23, 2019
  2. nedbat commented on Jun 23, 2019

    @nedbat
    ContributorAuthor

    Here is a gist showing the warning happening: https://gist.github.com/nedbat/2ba6834eab6d008dbd1c494c39f61bad (the warning about __main__ is a confusion between coverage's file and pytest's, and only happens with a -e install of coverage, so I am not concerned about it.)

  3. blueyed commented on Jun 23, 2019

    @blueyed
    Contributor

    Yes, controlling this in another way sounds useful.

    One solution for this might be to skip the warning during import of the (entrypoint) plugin (and also for -p coverage.pytest_plugin), only explicitly for __init__.py files seen during it (the import process).
    It should/would still happen for coverage.pytest_plugin itself then.

  4. ikappaki commented on May 12, 2025

    @ikappaki
    Contributor

    Hi, we've run into a similar issue with Basilisp, where the basilisp package includes a pytest plugin as part of its source tree:

    In pyrproject.toml

    [tool.poetry.plugins.pytest11]:
    basilisp_test_runner = "basilisp.contrib.pytest.testrunner"

    The package also provides a CLI that invokes pytest.main():
    From src/basilisp/cli.py:

    from basilisp import main as basilisp
    #...
    def test(
        parser: argparse.ArgumentParser,
        args: argparse.Namespace,
        extra: list[str],
    ) -> None:  # pragma: no cover
    #...
            sys.exit(pytest.main(args=list(extra)))

    However, since:

    1. basilisp declares a pytest plugin, enabling assertion rewriting, and
    2. the basilisp module is already imported when pytest.main() is called,

    pytest raises the warning::
    Module already imported so cannot be rewritten: basilisp

    We'd like to suppress this warning for basilisp, as assertion rewriting isn't needed here. While pytest supports suppressing warnings using the -W option:

    -W ignore:Module already imported so cannot be rewritten: basilisp:pytest.PytestAssertRewriteWarning:

    attempting this like so:

    from basilisp import main as basilisp
    #...
    def test(
        parser: argparse.ArgumentParser,
        args: argparse.Namespace,
        extra: list[str],
    ) -> None:  # pragma: no cover
    #...
          extra = [
                "-W",
                "ignore:Module already imported so cannot be rewritten:* basilisp:pytest.PytestAssertRewriteWarning",
            ] + extra
            sys.exit(pytest.main(args=list(extra)))

    fails with the error:

    ERROR: while parsing the following warning configuration:
     ignore:Module already imported so cannot be rewritten:* basilisp:pytest.PytestAssertRewriteWarning
     This error occurred:
     Traceback (most recent call last):
      File "d:\src\basilisp\.venv\Lib\site-packages\_pytest\config\__init__.py", line 1918, in parse_warning_filter
        category: type[Warning] = _resolve_warning_category(category_)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "d:\src\basilisp\.venv\Lib\site-packages\_pytest\config\__init__.py", line 1957, in _resolve_warning_category
        cat = getattr(m, klass)
              ^^^^^^^^^^^^^^^^^
    AttributeError: module 'builtins' has no attribute '* basilisp'
    

    this doesn't work because the message contains a literal : (as in ...rewritten: basilisp), which is treated as field separator in -W, corrupting the filter definition.

    Since wildcards aren't supported in -W filters one possible solution is to change the warning message in

    f"Module already imported so cannot be rewritten: {name}"

    to replace : for example with something like ;:

                  f"Module already imported so cannot be rewritten; {name}"

    So that the -W filter can now work (there is no extra : in the filter any more):
    -W ignore:Module already imported so cannot be rewritten; basilisp:pytest.PytestAssertRewriteWarning.

    Using : in warning messages makes it incompatible with some Python’s warning filter syntax, so replacing it would improve usability. I’m planning to raise a PR to suggest this change.

    Alternatives considered

    • Use Python's warning.filterwarnings() prior to calling pytest.main:
    import warnings
    warnings.filterwarnings("ignore", message="Module already imported so cannot be rewritten: basilisp", category=pytest.PytestAssertRewriteWarning)
    sys.exit(pytest.main(args=list(extra)))

    This doesn't work doesn't work because pytest disables all filters internally when emitting this warning:

    def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
    """Issue and handle a warning during the "configure" stage.
    During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item``
    function because it is not possible to have hook wrappers around ``pytest_configure``.
    This function is mainly intended for plugins that need to issue warnings during
    ``pytest_configure`` (or similar stages).
    :param warning: The warning instance.
    :param stacklevel: stacklevel forwarded to warnings.warn.
    """
    if self.pluginmanager.is_blocked("warnings"):
    return
    cmdline_filters = self.known_args_namespace.pythonwarnings or []
    config_filters = self.getini("filterwarnings")
    with warnings.catch_warnings(record=True) as records:
    warnings.simplefilter("always", type(warning))
    apply_warning_filters(config_filters, cmdline_filters)
    warnings.warn(warning, stacklevel=stacklevel)
    if records:
    frame = sys._getframe(stacklevel - 1)
    location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
    self.hook.pytest_warning_recorded.call_historic(
    kwargs=dict(
    warning_message=records[0],
    when="config",
    nodeid="",
    location=location,
    )
    )

    • Users adding explicit filterwarnings in their pyproject.toml:
    [tool.pytest.ini_options]
    filterwarnings = [
        "ignore:Module already imported so cannot be rewritten. basilisp:pytest.PytestAssertRewriteWarning",
    ]

    This works but requires the user to know and handle this technical limitation explicitly. Ideally, the warning wouldn’t be shown in the first place.

    Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: rewriterelated to the assertion rewrite mechanismtype: enhancementnew feature or API change, should be merged into features branch

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions