Bug: Error When Overriding Builtin Exception With Same Name

by ADMIN 60 views

Description of the bug

In the obspec project, a NotImplementedError exception is defined that subclasses from both obspec.exceptions.BaseError and the built-in NotImplementedError. This is done to ensure that isinstance works for both parents. However, when trying to override the built-in NotImplementedError with the same name, an error occurs.

ValueError: Cannot compute C3 linearization, inheritance cycle detected: obspec.exceptions.NotImplementedError -> obspec.exceptions.NotImplementedError

To Reproduce

To reproduce this bug, follow these steps:

  1. Clone the obspec repository using the following command:

git clone https://github.com/developmentseed/obspec

2. Navigate to the cloned repository:
   ```bash
cd obspec
  1. Checkout the specific commit that introduces the bug:

git checkout 35163ec32296ce4116eec0cf0565466af6f6952b

4. Run the `mkdocs build` command to trigger the error:
   ```bash
uv run mkdocs build

Full traceback

The full error message is as follows:

ERROR   -  Error reading page 'api/arrow.md': Cannot compute C3 linearization, inheritance cycle
           detected: obspec.exceptions.NotImplementedError -> obspec.exceptions.NotImplementedError
Traceback (most recent call last):
  File "/Users/kyle/tmp/obspec/.venv/bin/mkdocs", line 10, in <module>
    sys.exit(cli())
             ^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/click/core.py", line 1161, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/click/core.py", line 1082, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/click/core.py", line 1697, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/click/core.py", line 1443, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/click/core.py", line 788, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/mkdocs/__main__.py", line 288, in build_command
    build.build(cfg, dirty=not clean)
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/mkdocs/commands/build.py", line 310, in build
    _populate_page(file.page, config, files, dirty)
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/mkdocs/commands/build.py", line 167, in _populate_page
    page.render(config, files)
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/mkdocs/structure/pages.py", line 285, in render
    self.content = md.convert(self.markdown)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/markdown/core.py", line 357, in convert
    root = self.parser.parseDocument(self.lines).getroot()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/markdown/blockparser.py", line 117, in parseDocument
    self.parseChunk(self.root, '\n'.join(lines))
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/markdown/blockparser.py", line 136, in parseChunk
    self.parseBlocks(parent, text.split('\n\n'))
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/markdown/blockparser.py", line 158, in parseBlocks
    if processor.run(parent, blocks) is not False:
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/mkdocstrings/_internal/extension.py", line 126, in run
    html, handler, data = self._process_block(identifier, block, heading_level)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/mkdocstrings/_internal/extension.py", line 189, in _process_block
    data: CollectorItem = handler.collect(identifier, options)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/mkdocstrings_handlers/python/handler.py", line 234, in collect
    loader.load(
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/_griffe/loader.py", line 184, in load
    return self._post_load(top_module, obj_path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/_griffe/loader.py", line 199, in _post_load
    self.extensions.call("on_package_loaded", pkg=module, loader=self)
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/_griffe/extensions/base.py", line 313, in call
    getattr(extension, event)(**kwargs)
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/griffe_inherited_docstrings/extension.py", line 80, in on_package_loaded
    _inherit_docstrings(pkg, merge=self.merge, seen=set())
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/griffe_inherited_docstrings/extension.py", line 36, in _inherit_docstrings
    _inherit_docstrings(member, merge=merge, seen=seen)  # type: ignore[arg-type]
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/griffe_inherited_docstrings/extension.py", line 36, in _inherit_docstrings
    _inherit_docstrings(member, merge=merge, seen=seen)  # type: ignore[arg-type]
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/griffe_inherited_docstrings/extension.py", line 43, in _inherit_docstrings
    for parent in reversed(obj.mro()):  # type: ignore[attr-defined]
                           ^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/_griffe/models.py", line 2007, in mro
    return self._mro()[1:]  # remove self
           ^^^^^^^^^^^
  File "/Users/kyle/tmp/obspec/.venv/lib/python3.12/site-packages/_griffe/models.py", line 1998, in _mro
    raise ValueError(f"Cannot compute C3 linearization, inheritance cycle detected: {cycle}")
ValueError: Cannot compute C3 linearization, inheritance cycle detected: obspec.exceptions.NotImplementedError -> obspec.exceptions.NotImplementedError

Expected behavior

The expected behavior is that the code should not error when overriding the built-in NotImplementedError with the same name.

Environment information

The environment information is as follows:

  • System: macOS-15.3.1-arm64-arm-64bit
  • Python: cpython 3.12.7 (/Users/kyle/tmp/obspec/.venv/bin/python3)
  • Environment variables:
  • Installed packages:
    • griffe-inherited-docstrings v1.1.1

Additional context

This bug is related to the way Python handles inheritance and the C3 linearization algorithm. The error occurs when trying to override the built-in NotImplementedError with the same name, causing an inheritance cycle. This is an edge case and may not be a common issue, but it's still worth investigating and fixing.

Solution

To fix this bug, we need to avoid overriding the built-in NotImplementedError with the same name. Instead, we can use a different name for our custom exception. This will prevent the inheritance cycle and allow the code to run without errors.

class CustomNotImplementedError(obspec.exceptions.BaseError):
    pass

Q&A

Q: What is the bug?

A: The bug occurs when trying to override the built-in NotImplementedError exception with the same name, causing an inheritance cycle.

Q: What is an inheritance cycle?

A: An inheritance cycle occurs when a class inherits from another class, which in turn inherits from the first class, creating a loop. This can cause issues with the C3 linearization algorithm used by Python to resolve inheritance relationships.

Q: What is the C3 linearization algorithm?

A: The C3 linearization algorithm is a method used by Python to resolve inheritance relationships between classes. It is used to determine the order in which classes are inherited from one another.

Q: Why is the C3 linearization algorithm important?

A: The C3 linearization algorithm is important because it helps to resolve inheritance relationships between classes. Without it, Python would not be able to determine the correct order in which classes are inherited from one another, leading to issues with class inheritance.

Q: How can I avoid the inheritance cycle?

A: To avoid the inheritance cycle, you can use a different name for your custom exception. This will prevent the inheritance cycle and allow the code to run without errors.

Q: What is the solution to the bug?

A: The solution to the bug is to use a different name for your custom exception. This will prevent the inheritance cycle and allow the code to run without errors.

Q: Can I override the built-in NotImplementedError exception?

A: No, you should not override the built-in NotImplementedError exception with the same name. Instead, use a different name for your custom exception.

Q: What are the implications of this bug?

A: The implications of this bug are that it can cause issues with class inheritance and the C3 linearization algorithm. This can lead to errors and unexpected behavior in your code.

Q: How can I prevent this bug in the future?

A: To prevent this bug in the future, make sure to use a different name for your custom exception when overriding built-in exceptions.

Conclusion

In conclusion, the bug occurs when trying to override the built-in NotImplementedError exception with the same name, causing an inheritance cycle. To avoid this bug, use a different name for your custom exception. This will prevent the inheritance cycle and allow the code to run without errors.

Additional Resources