Skip to content

Commit 8b8e06b

Browse files
bdracoasvetlovwebknjaz
authored
Fix creating CIMultiDict from MultiDict not making keys case-insentive (#1112)
When `MultiDict` was passed to `CIMultiDict` the keys need to be converted fixes #1111 side note `_multidict_extend` doesn't appear to use `name` --------- Co-authored-by: Andrew Svetlov <andrew.svetlov@gmail.com> Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) <webknjaz@redhat.com>
1 parent 41cbc0d commit 8b8e06b

4 files changed

Lines changed: 94 additions & 6 deletions

File tree

CHANGES/1112.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed keys not becoming case-insensitive when :class:`multidict.CIMultiDict` is created by passing in a :class:`multidict.MultiDict` -- by :user:`bdraco`.

multidict/_multidict_py.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,8 @@ def _title(self, key: str) -> str:
456456

457457

458458
class _CIMixin:
459+
_ci: bool = True
460+
459461
def _key(self, key: str) -> str:
460462
if type(key) is istr:
461463
return key
@@ -477,6 +479,7 @@ def _title(self, key: str) -> str:
477479

478480
class _Base(MultiMapping[_V]):
479481
_impl: _Impl[_V]
482+
_ci: bool = False
480483

481484
@abstractmethod
482485
def _key(self, key: str) -> str: ...
@@ -632,9 +635,13 @@ def _extend(
632635
) -> None:
633636
if arg:
634637
if isinstance(arg, (MultiDict, MultiDictProxy)):
635-
items = arg._impl._items
638+
if self._ci is not arg._ci:
639+
items = [(self._title(k), k, v) for _, k, v in arg._impl._items]
640+
else:
641+
items = arg._impl._items
642+
if kwargs:
643+
items = items.copy()
636644
if kwargs:
637-
items = items.copy()
638645
for key, value in kwargs.items():
639646
items.append((self._title(key), key, value))
640647
else:

multidict/_multilib/pair_list.h

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,23 +1132,54 @@ static inline int
11321132
pair_list_update_from_pair_list(pair_list_t *list, PyObject* used, pair_list_t *other)
11331133
{
11341134
Py_ssize_t pos;
1135+
Py_hash_t hash;
1136+
PyObject *identity = NULL;
1137+
PyObject *key = NULL;
1138+
bool recalc_identity = list->calc_ci_indentity != other->calc_ci_indentity;
11351139

11361140
for (pos = 0; pos < other->size; pos++) {
11371141
pair_t *pair = other->pairs + pos;
1142+
if (recalc_identity) {
1143+
identity = pair_list_calc_identity(list, pair->key);
1144+
if (identity == NULL) {
1145+
goto fail;
1146+
}
1147+
hash = PyObject_Hash(identity);
1148+
if (hash == -1) {
1149+
goto fail;
1150+
}
1151+
/* materialize key */
1152+
key = pair_list_calc_key(other, pair->key, identity);
1153+
if (key == NULL) {
1154+
goto fail;
1155+
}
1156+
} else {
1157+
identity = pair->identity;
1158+
hash = pair->hash;
1159+
key = pair->key;
1160+
}
11381161
if (used != NULL) {
1139-
if (_pair_list_update(list, pair->key, pair->value, used,
1140-
pair->identity, pair->hash) < 0) {
1162+
if (_pair_list_update(list, key, pair->value, used,
1163+
identity, hash) < 0) {
11411164
goto fail;
11421165
}
11431166
} else {
1144-
if (_pair_list_add_with_hash(list, pair->identity, pair->key,
1145-
pair->value, pair->hash) < 0) {
1167+
if (_pair_list_add_with_hash(list, identity, key,
1168+
pair->value, hash) < 0) {
11461169
goto fail;
11471170
}
11481171
}
1172+
if (recalc_identity) {
1173+
Py_CLEAR(identity);
1174+
Py_CLEAR(key);
1175+
}
11491176
}
11501177
return 0;
11511178
fail:
1179+
if (recalc_identity) {
1180+
Py_CLEAR(identity);
1181+
Py_CLEAR(key);
1182+
}
11521183
return -1;
11531184
}
11541185

tests/test_multidict.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
MultiDictProxy,
1919
MultiMapping,
2020
MutableMultiMapping,
21+
istr,
2122
)
2223

2324
_T = TypeVar("_T")
@@ -1223,3 +1224,51 @@ def test_create_multidict_from_existing_multidict_new_pairs() -> None:
12231224
new = MultiDict(original, h4="header4")
12241225
assert "h4" in new
12251226
assert "h4" not in original
1227+
1228+
1229+
def test_convert_multidict_to_cimultidict_and_back(
1230+
case_sensitive_multidict_class: type[MultiDict[str]],
1231+
case_insensitive_multidict_class: type[CIMultiDict[str]],
1232+
case_insensitive_str_class: type[istr],
1233+
) -> None:
1234+
"""Test conversion from MultiDict to CIMultiDict."""
1235+
start_as_md = case_sensitive_multidict_class(
1236+
[("KEY", "value1"), ("key2", "value2")]
1237+
)
1238+
assert start_as_md.get("KEY") == "value1"
1239+
assert start_as_md["KEY"] == "value1"
1240+
assert start_as_md.get("key2") == "value2"
1241+
assert start_as_md["key2"] == "value2"
1242+
start_as_cimd = case_insensitive_multidict_class(
1243+
[("KEY", "value1"), ("key2", "value2")]
1244+
)
1245+
assert start_as_cimd.get("key") == "value1"
1246+
assert start_as_cimd["key"] == "value1"
1247+
assert start_as_cimd.get("key2") == "value2"
1248+
assert start_as_cimd["key2"] == "value2"
1249+
converted_to_ci = case_insensitive_multidict_class(start_as_md)
1250+
assert converted_to_ci.get("key") == "value1"
1251+
assert converted_to_ci["key"] == "value1"
1252+
assert converted_to_ci.get("key2") == "value2"
1253+
assert converted_to_ci["key2"] == "value2"
1254+
converted_to_md = case_sensitive_multidict_class(converted_to_ci)
1255+
assert all(type(k) is case_insensitive_str_class for k in converted_to_ci.keys())
1256+
assert converted_to_md.get("KEY") == "value1"
1257+
assert converted_to_md["KEY"] == "value1"
1258+
assert converted_to_md.get("key2") == "value2"
1259+
assert converted_to_md["key2"] == "value2"
1260+
1261+
1262+
def test_convert_multidict_to_cimultidict_eq(
1263+
case_sensitive_multidict_class: type[MultiDict[str]],
1264+
case_insensitive_multidict_class: type[CIMultiDict[str]],
1265+
) -> None:
1266+
"""Test compare after conversion from MultiDict to CIMultiDict."""
1267+
original = case_sensitive_multidict_class(
1268+
[("h1", "header1"), ("h2", "header2"), ("h3", "header3")]
1269+
)
1270+
assert case_insensitive_multidict_class(
1271+
original
1272+
) == case_insensitive_multidict_class(
1273+
[("H1", "header1"), ("H2", "header2"), ("H3", "header3")]
1274+
)

0 commit comments

Comments
 (0)