Skip to content

bug: attributes using dict comprehension are incorrectly displayed#311

@niooss-ledger

Description

@niooss-ledger

Description of the bug

When displaying (with some options detailed later) a module attribute initialized with dictionary comprehension, the documentation shows incorrect Python code and random characters.

For example,

TEST_0_INDEX_BY_KEY={obj.key: objforobjin []}

Is displayed in the documentation generated by mkdocs as:

TEST_0_INDEX_BY_KEY={(key): _Hlforobjin []}

The options I used to trigger this behaviour were separate_signature: true and signature_crossrefs: true.

To Reproduce

I reproduced using 3 files:

  • test_dict.py
TEST_0_INDEX_BY_KEY={obj.key: objforobjin []} """Showing `(key): <random characters>` instead of `obj.key: obj`."""TEST_1_ACCESS_MEMBER={obj: obj.memberforobjin []} """Showing `obj: (member)` instead of `obj: obj.member`."""TEST_2_LONG_NAME={some_object_with_a_long_name: some_object_with_a_long_nameforsome_object_with_a_long_namein [] } """Showing `some_object_with_a_long_name: <more random characters>` instead of `some_object_with_a_long_name: some_object_with_a_long_name`."""
  • mkdocs.yml
site_name: Test Python dict comprehensionplugins: - mkdocstrings: handlers: python: options: separate_signature: truesignature_crossrefs: true
  • docs/index.md
::: test_dict

The 3 files can be created with this command (which uses a base64-encoded tar.gz archive):

echo H4sIAAAAAAACA+3WUWvbMBAAYD/7Vxx5ah/myYljg+kGbWfG2JKNJQ8rYyiurcReI8tICl0Y++87OzFLN3Cg0IzCfS+OrYvucO5QrDCW52VmvXrrPBGGwjBor+jvK4uiyPHHLPKDMAiZ7zB/6I98B5hzAhtjUw3gaKVsX9yx9WdqnszmnPF30zfJF351w98nN/AKfqrb796d2MaAH2CpdHstK/j67Zc7GAxmhbovqxUszjDoPIYLnVa5kpAVqU4zK7R5vcBwY0Wag1rC4nC/hYc7uG6b2eeX19fJbMYnyeQq+bxP3YZ5Ushbofuyt6Fnu7jzfxIe7nKYc8g/fJy+5dPLSdLkcwEZJQXHcJFZfl/agqd8raoVr1Ip4t7V9utNjX1Bu+Ldh9X357yQSgs49l4fX/fujTjyLleZ8bZy/XQ9dmz+Ryzo5n8cDUc4//44YjT/p2BKK/bdMsejAD5tbaEqaE4EyJSstShEZUpVuW693qyw92Ls+Bew6xursZPbJ40Cm3WNPdrdA9Ttbn/uAVRtcTNz+AinT9TY31iIKVdVajcaq7F6Ix4GdWs808oYLZZmH+WQR2t+xZdllYsfnsz/0/wzP+zmfzgehe35H4xp/k8hjnGIuv+ANEmEEEIIIYQQQgghhBBCCCGEEPJs/QaXqtegACgAAA== | base64 -d | tar -xz 

Then, install mkdocs and mkdocstrings-python and launch the server:

pip install mkdocs mkdocstrings-python mkdocs serve

http://127.0.0.1:8000/ shows:

Image

The 3 Python snippets are:

TEST_0_INDEX_BY_KEY={(key): _Z5forobjin []} TEST_1_ACCESS_MEMBER={obj: (member)forobjin []} TEST_2_LONG_NAME={some_object_with_a_long_name: _6Jpv7OmLiQU4bm2xsgNeOyqDsdmforsome_object_with_a_long_namein []}

It is also possible to run mkdocs build and read site/index.html:

Extract of site/index.html
<h2id="test_dict.TEST_0_INDEX_BY_KEY" class="doc doc-heading"><spanclass="doc doc-object-name doc-attribute-name">TEST_0_INDEX_BY_KEY</span><spanclass="doc doc-labels"><smallclass="doc doc-label doc-label-module-attribute"><code>module-attribute</code></small></span></h2><preclass="highlight"><codeclass="language-python doc-signature">TEST_0_INDEX_BY_KEY ={(<spantitle="obj.key">key</span>): _xEfor <spantitle="obj">obj</span> in []}</code></pre><divclass="doc doc-contents "><p>Showing <code>(key): &lt;random characters&gt;</code> instead of <code>obj.key: obj</code>.</p></div></div><divclass="doc doc-object doc-attribute"><h2id="test_dict.TEST_1_ACCESS_MEMBER" class="doc doc-heading"><spanclass="doc doc-object-name doc-attribute-name">TEST_1_ACCESS_MEMBER</span><spanclass="doc doc-labels"><smallclass="doc doc-label doc-label-module-attribute"><code>module-attribute</code></small></span></h2><preclass="highlight"><codeclass="language-python doc-signature">TEST_1_ACCESS_MEMBER ={<spantitle="obj">obj</span>: (<spantitle="obj.member">member</span>)for <spantitle="obj">obj</span> in []}</code></pre><divclass="doc doc-contents "><p>Showing <code>obj: (member)</code> instead of <code>obj: obj.member</code>.</p></div></div><divclass="doc doc-object doc-attribute"><h2id="test_dict.TEST_2_LONG_NAME" class="doc doc-heading"><spanclass="doc doc-object-name doc-attribute-name">TEST_2_LONG_NAME</span><spanclass="doc doc-labels"><smallclass="doc doc-label doc-label-module-attribute"><code>module-attribute</code></small></span></h2><preclass="highlight"><codeclass="language-python doc-signature">TEST_2_LONG_NAME ={<spantitle="some_object_with_a_long_name">some_object_with_a_long_name</span>: _7kTCpbiedqLcznZoOnrjKhe3D9Gfor <spantitle="some_object_with_a_long_name">some_object_with_a_long_name</span> in []}</code></pre><divclass="doc doc-contents "><p>Showing <code>some_object_with_a_long_name: &lt;more random characters&gt;</code> instead of <code>some_object_with_a_long_name: some_object_with_a_long_name</code>.</p></div></div>
Without `separate_signature: true`, this generates code which is mostly right (a space is missing before `for`, and some extra parentheses are added):
TEST_0_INDEX_BY_KEY={(obj.key): objforobjin []} TEST_1_ACCESS_MEMBER={obj: (obj.member)forobjin []} TEST_2_LONG_NAME={some_object_with_a_long_name: some_object_with_a_long_nameforsome_object_with_a_long_namein []}

With separate_signature: true and without signature_crossrefs: true, obj. disappears from the first two reproducers:

TEST_0_INDEX_BY_KEY={(key): objforobjin []} TEST_1_ACCESS_MEMBER={obj: (member)forobjin []} TEST_2_LONG_NAME={some_object_with_a_long_name: some_object_with_a_long_nameforsome_object_with_a_long_namein []}

Expected behavior

I expect the Python code displayed by mkdocs to be consistent.

Environment information

I tested in a Python 3.13 container with the current git main version:

podman run --rm --net=host -it docker.io/library/python:3.13 bash pip install 'git+https://github.com/mkdocs/mkdocs' pip install 'git+https://github.com/mkdocstrings/python'

This installed:

$ pip freezeclick==8.3.0colorama==0.4.6ghp-import==2.1.0griffe==1.14.0Jinja2==3.1.6Markdown==3.9MarkupSafe==3.0.3mergedeep==1.3.4mkdocs @ git+https://github.com/mkdocs/mkdocs@f68e5fc18d3cd3811dbddfdeda1a57721f9e2e4emkdocs-autorefs==1.4.3mkdocs-get-deps==0.2.0mkdocstrings==0.30.1mkdocstrings-python @ git+https://github.com/mkdocstrings/python@f25b2ecf73f30ac0ea836bec1aa60e87a360e23apackaging==25.0pathspec==0.12.1platformdirs==4.4.0pymdown-extensions==10.16.1python-dateutil==2.9.0.post0PyYAML==6.0.3pyyaml_env_tag==1.1six==1.17.0watchdog==6.0.0
python -m mkdocstrings_handlers.python._internal.debug # | xclip -selection clipboard
  • System: Linux-6.8.0-85-generic-x86_64-with-glibc2.39
  • Python: cpython 3.13.0 (/usr/local/bin/python)
  • Environment variables:
  • Installed packages:
    • mkdocstrings-python v1.18.3.dev1+gf25b2ec

I also tested with uv and several Python versions:

uv run --python python3.13 --with mkdocs --with mkdocstrings-python mkdocs serve uv run --python python3.14 --with mkdocs --with mkdocstrings-python mkdocs serve

Additional context

I encountered this issue in a project where several classes are defined (ClsA, ClsB, ClsC), a global variable listes the classes (ALL_CLASSES = (ClsA, ClsB, ClsC)) and another global variable enables getting a class through their name:

CLASS_BY_NAME={cls.name: clsforclsinALL_CLASSES}

The documentation generated by mkdocs was buggy and this is what led to the first minimal reproducer I shared (TEST_0_INDEX_BY_KEY ={obj.key: obj for obj in []}).

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions