diff --git a/pipenv/project.py b/pipenv/project.py index a6bcc87a72..dab4915e35 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -15,6 +15,7 @@ from urllib.parse import unquote, urljoin from pipenv.utils.constants import VCS_LIST +from pipenv.vendor.tomlkit.items import SingleKey, Table try: import tomllib as toml @@ -1107,12 +1108,23 @@ def get_package_name_in_pipfile(self, package_name, category): return name return None - def _sort_category(self, category): - # toml tables won't maintain sorted dictionary order - # so construct the table in the order that we need + def _sort_category(self, category) -> Table: + # copy table or create table from dict-like object table = tomlkit.table() - for key, value in sorted(category.items()): - table.add(key, value) + if isinstance(category, Table): + table.update(category.value) + else: + table.update(category) + + # sort the table internally + table._value._body.sort(key=lambda t: t[0] and t[0].key or "") + for index, (key, _) in enumerate(table._value._body): + assert isinstance(key, SingleKey) + indices = table._value._map[key] + if isinstance(indices, tuple): + table._value._map[key] = (index,) + indices[1:] + else: + table._value._map[key] = index return table diff --git a/pipenv/utils/toml.py b/pipenv/utils/toml.py index 1f69de459a..b97d32d3bd 100644 --- a/pipenv/utils/toml.py +++ b/pipenv/utils/toml.py @@ -1,8 +1,9 @@ from typing import Union from pipenv.vendor.plette.models import Package, PackageCollection -from pipenv.vendor.tomlkit.container import Container +from pipenv.vendor.tomlkit.container import Container, OutOfOrderTableProxy from pipenv.vendor.tomlkit.items import AoT, Array, Bool, InlineTable, Item, String, Table +from pipenv.vendor.tomlkit.toml_document import TOMLDocument try: import tomllib as toml @@ -33,26 +34,32 @@ def cleanup_toml(tml): return toml -def convert_toml_outline_tables(parsed, project): +def convert_toml_outline_tables(parsed: TOMLDocument, project) -> TOMLDocument: """Converts all outline tables to inline tables.""" def convert_tomlkit_table(section): - result = section.copy() - if isinstance(section, tomlkit.items.Table): + result: Table = tomlkit.table() + if isinstance(section, Table): body = section.value._body - elif isinstance(section, tomlkit.container.OutOfOrderTableProxy): + elif isinstance(section, OutOfOrderTableProxy): body = section._internal_container._body else: - body = section._body - for index, (key, value) in enumerate(body): + assert not hasattr(section, "_body") + body = section + + index: int = 0 + for key, value in body: if not key: continue - if hasattr(value, "keys") and not isinstance( - value, tomlkit.items.InlineTable - ): + if hasattr(value, "keys") and not isinstance(value, InlineTable): table = tomlkit.inline_table() table.update(value.value) - result.value._body[index] = (key, table.value) + key.sep = " = " # add separator because it did not exist before + result.append(key, table) + else: + result.append(key, value) + index += 1 + return result def convert_toml_table(section): @@ -66,10 +73,10 @@ def convert_toml_table(section): result[package] = table return result - is_tomlkit_parsed = isinstance(parsed, tomlkit.container.Container) + is_tomlkit_parsed = isinstance(parsed, Container) for section in project.get_package_categories(): table_data = parsed.get(section, {}) - if not table_data: + if table_data is None: continue if is_tomlkit_parsed: result = convert_tomlkit_table(table_data)