Repository: mkdocs/mkdocs

Issue:
Can't use `@dataclass` in the hook
> **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`](https://docs.python.org/3.11/library/dataclasses.html) in the hook, then mkdocs can't execute it because the module is not in `sys.modules`.

## Minimal Example

```python
# 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
```

```yaml
# mkdocs.yml
# …
hooks:
  - my_hook.py
```

Now `mkdocs build`.

```python
# (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`](https://docs.python.org/3/library/typing.html#typing.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.

https://github.com/mkdocs/mkdocs/blob/56b235a8ad43f2300d17f87e6fa4de7a3d764397/mkdocs/config/config_options.py#L1051-L1062

I am not an expert on `importlib` or CPython, but [all examples in Python Doc](https://docs.python.org/3.11/library/importlib.html#examples) add the module to `sys.module`.

```python
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:

```python
>>> 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


Discussion:

Resolving patch:
diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py
--- a/mkdocs/config/config_options.py
+++ b/mkdocs/config/config_options.py
@@ -1057,6 +1057,7 @@ def _load_hook(self, name, path):
         if spec is None:
             raise ValidationError(f"Cannot import path '{path}' as a Python module")
         module = importlib.util.module_from_spec(spec)
+        sys.modules[name] = module
         if spec.loader is None:
             raise ValidationError(f"Cannot import path '{path}' as a Python module")
         spec.loader.exec_module(module)


Test patch: