ipython / ipython/traitlets

in get_type_hints(Appliction), TypeError: <class 'traitlets.traitlets.Dict'> is not a generic class

Open
#904 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
653
Forks
217
Avg merge
2d 21h
Merged PRs (30d)
2

Description

The expression `get_type_hints(Application)` raises an exception, given the class `traitlets.config.application.Application` or a subclass.

### Minimal Example

file `traitlets_debug_00.py`
```python
from traitlets.config.application import Application
from typing_extensions import get_type_hints

if __name__ == "__main__":
hints = get_type_hints(Application)
```

Traceback from `__main__`:
```
Traceback (most recent call last):
File ".../traitlets_debug_00.py", line 6, in
hints = get_type_hints(Application)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib64/python3.11/typing.py", line 2305, in get_type_hints
value = _eval_type(value, base_globals, base_locals)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib64/python3.11/typing.py", line 359, in _eval_type
return t._evaluate(globalns, localns, recursive_guard)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib64/python3.11/typing.py", line 857, in _evaluate
eval(self.__forward_code__, globalns, localns),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "", line 1, in
File "/usr/lib64/python3.11/typing.py", line 344, in inner
return func(*args, **kwds)
^^^^^^^^^^^^^^^^^^^
File "/usr/lib64/python3.11/typing.py", line 1807, in __class_getitem__
_check_generic(cls, params, len(cls.__parameters__))
File "/usr/lib64/python3.11/typing.py", line 271, in _check_generic
raise TypeError(f"{cls} is not a generic class")
TypeError: is not a generic class
```

### Analysis

in [`traitlets.traitlets` line 3842](https://github.com/ipython/traitlets/blob/5cf34f6c42b6e39aa1e53394443efca3d7329e3b/traitlets/traitlets.py#L3842C1-L3842C36), the `Dict` TraitType is defined like as follows:
```py
class Dict(Instance["dict[K, V]"]):
...
```

In [`traitlets.config.application`, line 403](https://github.com/ipython/traitlets/blob/5cf34f6c42b6e39aa1e53394443efca3d7329e3b/traitlets/config/application.py#L403), the type is applied as:
```py
subcommands: dict[str, t.Any] | Dict[str, t.Any] = Dict()
```

The `Dict` TraitType has not been defined as a subscriptable type, however.

### Patch?

At an instance scope, the value of the `subcommands` attribute should generally be a Python dict, while at a class scope, the attribute would be represented with a TraitType descriptor, namely an instance of the Dict TraitType.

As per the note about the syntax of the `subcommands` value in the source code where the attr is defined, the `subcommands` attr could be defined as follows:
```py
subcommands: dict[str, tuple[Callable, str]] = Dict()
```

In effect, this would remove the class-scoped Dict TraitType descriptor from the type hint for the instance-scoped `subcommands` attribute value, also providing some more detail about the syntax of the attribute value.

The initialization of the `subcommands` descriptor object would not be changed with this definition.

### Workarounds?

As one approach, the following can be used to patch the annotation in the base Application class, while providing an `Application`-like class with the patched type hint:

```python
from traitlets.config.application import Application
from traitlets.traitlets import Dict
from collections.abc import Callable
import typing as t

class ApplicationShim(Application):
subcommands: dict[str, tuple[t.Union[str, Callable[[t.Self], t.Self]], str]] = Dict()

Application.__annotations__['subcommands'] = ApplicationShim.__annotations__['subcommands']
```

The type hint in this patch is referenced to how the attribute value is applied in `Application.initialize_subcommand()` with examples for how the `subcommands` value is defined as a class variable, in the following:
- `IPython.core.profileapp.ProfileApp`
- `jupyter_client.kernelspecapp.KernelSpecApp`
- `ipykernel.kernelapp.IPKernelApp`
- `ipyparallel.cluster.app.IPCluster`

To apply the patched type hint insofar as may be visible within editor environments for instance, the class `ApplicationShim` could be used as a base class in lieu of `Application`. The patch onto the base `subcommands` annotation would be the main thing, however, insofar as for application with `get_type_hints()`

### Impacts?

This exception is encountered when calling `get_type_hints(Application)`.

This may affect some reflection-oriented user code and could possibly affect type analysis for the Application class

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.