|
34 | 34 | from pathlib import Path |
35 | 35 | from typing import ClassVar |
36 | 36 |
|
| 37 | + from tomlkit.container import Container |
| 38 | + from tomlkit.items import Item |
37 | 39 | from typing_extensions import Self |
38 | 40 |
|
39 | 41 | from usethis._io import Key |
@@ -123,7 +125,7 @@ def __getitem__(self, item: Sequence[Key]) -> Any: |
123 | 125 |
|
124 | 126 | return d |
125 | 127 |
|
126 | | - def set_value( # noqa: PLR0912 |
| 128 | + def set_value( |
127 | 129 | self, *, keys: Sequence[Key], value: Any, exists_ok: bool = False |
128 | 130 | ) -> None: |
129 | 131 | """Set a value in the TOML file. |
@@ -155,40 +157,13 @@ def set_value( # noqa: PLR0912 |
155 | 157 | d, parent = d[key], d |
156 | 158 | shared_keys.append(key) |
157 | 159 | except KeyError: |
158 | | - # The old configuration should be kept for all ID keys except the |
159 | | - # final/deepest one which shouldn't exist anyway since we checked as much, |
160 | | - # above. For example, if there is [tool.ruff] then we shouldn't overwrite it |
161 | | - # with [tool.deptry]; they should coexist. So under the "tool" key, we need |
162 | | - # to "merge" the two dicts. |
163 | | - |
164 | | - if len(keys) <= 3: |
165 | | - contents = value |
166 | | - for key in reversed(keys): |
167 | | - contents = {key: contents} |
168 | | - toml_document = mergedeep.merge(toml_document, contents) |
169 | | - assert isinstance(toml_document, TOMLDocument) |
170 | | - else: |
171 | | - # Note that this alternative logic is just to avoid a bug: |
172 | | - # https://github.com/nathanjmcdougall/usethis-python/issues/507 |
173 | | - TypeAdapter(dict).validate_python(d) |
174 | | - assert isinstance(d, dict) |
175 | | - |
176 | | - unshared_keys = keys[len(shared_keys) :] |
177 | | - |
178 | | - if len(shared_keys) == 1: |
179 | | - # In this case, we need to "seed" the section to avoid another bug: |
180 | | - # https://github.com/nathanjmcdougall/usethis-python/issues/558 |
181 | | - |
182 | | - placeholder = {keys[0]: {keys[1]: {}}} |
183 | | - toml_document = mergedeep.merge(toml_document, placeholder) # type: ignore[reportArgumentType] |
184 | | - |
185 | | - contents = value |
186 | | - for key in reversed(unshared_keys[1:]): |
187 | | - contents = {key: contents} |
188 | | - |
189 | | - d[keys[1]] = contents |
190 | | - else: |
191 | | - d[_get_unified_key(unshared_keys)] = value |
| 160 | + _set_value_in_existing( |
| 161 | + toml_document=toml_document, |
| 162 | + current_container=d, |
| 163 | + keys=keys, |
| 164 | + current_keys=shared_keys, |
| 165 | + value=value, |
| 166 | + ) |
192 | 167 | else: |
193 | 168 | if not exists_ok: |
194 | 169 | # The configuration is already present, which is not allowed. |
@@ -331,6 +306,50 @@ def remove_from_list(self, *, keys: Sequence[Key], values: list[Any]) -> None: |
331 | 306 | self.commit(toml_document) |
332 | 307 |
|
333 | 308 |
|
| 309 | +def _set_value_in_existing( |
| 310 | + *, |
| 311 | + toml_document: TOMLDocument, |
| 312 | + current_container: TOMLDocument | Item | Container, |
| 313 | + keys: Sequence[Key], |
| 314 | + current_keys: Sequence[Key], |
| 315 | + value: Any, |
| 316 | +) -> None: |
| 317 | + # The old configuration should be kept for all ID keys except the |
| 318 | + # final/deepest one which shouldn't exist anyway since we checked as much, |
| 319 | + # above. For example, if there is [tool.ruff] then we shouldn't overwrite it |
| 320 | + # with [tool.deptry]; they should coexist. So under the "tool" key, we need |
| 321 | + # to "merge" the two dicts. |
| 322 | + |
| 323 | + if len(keys) <= 3: |
| 324 | + contents = value |
| 325 | + for key in reversed(keys): |
| 326 | + contents = {key: contents} |
| 327 | + toml_document = mergedeep.merge(toml_document, contents) # type: ignore[reportAssignmentType] |
| 328 | + assert isinstance(toml_document, TOMLDocument) |
| 329 | + else: |
| 330 | + # Note that this alternative logic is just to avoid a bug: |
| 331 | + # https://github.com/nathanjmcdougall/usethis-python/issues/507 |
| 332 | + TypeAdapter(dict).validate_python(current_container) |
| 333 | + assert isinstance(current_container, dict) |
| 334 | + |
| 335 | + unshared_keys = keys[len(current_keys) :] |
| 336 | + |
| 337 | + if len(current_keys) == 1: |
| 338 | + # In this case, we need to "seed" the section to avoid another bug: |
| 339 | + # https://github.com/nathanjmcdougall/usethis-python/issues/558 |
| 340 | + |
| 341 | + placeholder = {keys[0]: {keys[1]: {}}} |
| 342 | + toml_document = mergedeep.merge(toml_document, placeholder) # type: ignore[reportArgumentType] |
| 343 | + |
| 344 | + contents = value |
| 345 | + for key in reversed(unshared_keys[1:]): |
| 346 | + contents = {key: contents} |
| 347 | + |
| 348 | + current_container[keys[1]] = contents # type: ignore[reportAssignmentType] |
| 349 | + else: |
| 350 | + current_container[_get_unified_key(unshared_keys)] = value |
| 351 | + |
| 352 | + |
334 | 353 | def _validate_keys(keys: Sequence[Key]) -> list[str]: |
335 | 354 | """Validate the keys. |
336 | 355 |
|
|
0 commit comments