Describe the discrepancy
The documentation for istr describes it as a case-folded string with case-insensitive comparison, but in current releases istr behaves as a plain str: equality and hashing are case-sensitive, and the case-insensitive folding only happens inside CIMultiDict internals (via the __is_istr__ / __istr_identity__ markers).
Docs (docs/multidict.rst):
- "Create a new case-folded string object from the given object"
- "..versionchanged:: 3.7 —
istr doesn't title-case its argument anymore but uses internal lower-cased data for fast case-insensitive comparison."
Reproducer
>>> from multidict import istr, CIMultiDict
>>> istr("Key") == "key"
False
>>> hash(istr("Key")) == hash("key")
False
>>> len({istr("Header"), istr("HEADER"), "header"})
3
>>> str(istr("Content-Type"))
'Content-Type'
Case-insensitive lookup still works through the dict itself:
>>> CIMultiDict({"Content-Type": "x"})["content-type"]
'x'
Observed vs documented
| Documented |
Actual (6.7.1) |
| "case-folded string object" |
original case preserved (str(istr('Key')) == 'Key') |
| "internal lower-cased data for ... comparison" |
no __eq__/__hash__ override; direct comparisons are plain-str |
Same behavior in 6.1.0 and in the pure-Python fallback (class istr(str) carries only marker attributes), so this is long-standing rather than a recent regression.
Question
Which contract is intended going forward?
- If the marker-based design (folding handled by
CIMultiDict only) is final, the istr section of the docs could be reworded so users don't expect istr('Key') == 'key' or set/dict deduplication by folded value.
- If the documented per-object semantics are still the goal, then
__eq__/__hash__ would need to compare on the folded identity.
Happy to send a docs PR for option 1 if that is the intended reading.
Versions
- multidict 6.7.1 (C extension) and pure-Python fallback: identical behavior
- Python 3.13
Describe the discrepancy
The documentation for
istrdescribes it as a case-folded string with case-insensitive comparison, but in current releasesistrbehaves as a plainstr: equality and hashing are case-sensitive, and the case-insensitive folding only happens insideCIMultiDictinternals (via the__is_istr__/__istr_identity__markers).Docs (docs/multidict.rst):
istrdoesn't title-case its argument anymore but uses internal lower-cased data for fast case-insensitive comparison."Reproducer
Case-insensitive lookup still works through the dict itself:
Observed vs documented
str(istr('Key')) == 'Key')__eq__/__hash__override; direct comparisons are plain-strSame behavior in 6.1.0 and in the pure-Python fallback (
class istr(str)carries only marker attributes), so this is long-standing rather than a recent regression.Question
Which contract is intended going forward?
CIMultiDictonly) is final, theistrsection of the docs could be reworded so users don't expectistr('Key') == 'key'or set/dict deduplication by folded value.__eq__/__hash__would need to compare on the folded identity.Happy to send a docs PR for option 1 if that is the intended reading.
Versions