Skip to content

Can't use @dataclass in the hook #3141

@YDX-2147483647

Description

@YDX-2147483647

Note: I'm not sure whether this is a mkdos issue. So feel free to close it if it isn't.

If I use @dataclass in the hook, then mkdocs can't execute it because the module is not in sys.modules.

Minimal Example

# my_hook.py
from __future__ import annotations  # I've no idea why this matters, but it does… (@_@)

from dataclasses import dataclass


@dataclass
class Foo:
    bar: str
# mkdocs.yml
#
hooks:
  - my_hook.py

Now mkdocs build.

# (skip many lines)

  File "~\AppData\Local\Programs\Python\Python311\Lib\site-packages\mkdocs\config\base.py", line 55, in validate
    return self.run_validation(value)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "~\AppData\Local\Programs\Python\Python311\Lib\site-packages\mkdocs\config\config_options.py", line 1048, in run_validation
    hooks[name] = self._load_hook(name, path)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "~\AppData\Local\Programs\Python\Python311\Lib\site-packages\mkdocs\config\config_options.py", line 1061, in _load_hook
    spec.loader.exec_module(module)

# (skip many lines)

  File "~\AppData\Local\Programs\Python\Python311\Lib\dataclasses.py", line 712, in _is_type  
    ns = sys.modules.get(cls.__module__).__dict__
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute '__dict__'. Did you mean: '__dir__'?

Workaround (for mkdocs users)

Try TypedDict or normal class + __init__().

Possible Cause

The module is executed (exec_module), but not added to sys.module. Therefore when dataclass calls sys.modules.get(cls.__module__), it gets nothing.

@functools.lru_cache(maxsize=None)
def _load_hook(self, name, path):
import importlib.util
spec = importlib.util.spec_from_file_location(name, path)
if spec is None:
raise ValidationError(f"Cannot import path '{path}' as a Python module")
module = importlib.util.module_from_spec(spec)
if spec.loader is None:
raise ValidationError(f"Cannot import path '{path}' as a Python module")
spec.loader.exec_module(module)
return module

I am not an expert on importlib or CPython, but all examples in Python Doc add the module to sys.module.

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module  # 👈 Here
spec.loader.exec_module(module)

I can reproduce the errors in Python REPL:

>>> import sys
>>> from importlib import util  
>>> spec = util.spec_from_file_location('my_hook', './my_hook.py')

>>> spec.loader.exec_module(module)
# (skip many lines)
  File "~\AppData\Local\Programs\Python\Python311\Lib\dataclasses.py", line 712, in _is_type  
    ns = sys.modules.get(cls.__module__).__dict__
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute '__dict__'. Did you mean: '__dir__'?

>>> module = util.module_from_spec(spec)
>>> spec.loader.exec_module(module)
>>>

Additional Information

  • Windows 10
  • Python 3.11.2 (Update: and 3.11.3)
  • mkdocs 1.4.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions