diff --git a/morgan/__init__.py b/morgan/__init__.py index 7ff12cb..6872546 100644 --- a/morgan/__init__.py +++ b/morgan/__init__.py @@ -20,7 +20,7 @@ from morgan import configurator, metadata, server from morgan.__about__ import __version__ -from morgan.utils import Cache, to_single_dash, touch_file +from morgan.utils import Cache, ListExtendingOrderedDict, to_single_dash, touch_file PYPI_ADDRESS = "https://pypi.org/simple/" PREFERRED_HASH_ALG = "sha256" @@ -44,9 +44,11 @@ def __init__(self, args: argparse.Namespace): self.index_path = args.index_path self.index_url = args.index_url self.mirror_all_versions: bool = args.mirror_all_versions - self.package_type_regex: str = args.package_type_regex - self.config = configparser.ConfigParser() + self.config = configparser.ConfigParser( + strict=False, dict_type=ListExtendingOrderedDict + ) self.config.read(args.config) + self.package_type_regex: str = args.package_type_regex self.envs = {} self._supported_pyversions = [] self._supported_platforms = [] diff --git a/morgan/utils.py b/morgan/utils.py index b33048d..af9c9c2 100644 --- a/morgan/utils.py +++ b/morgan/utils.py @@ -1,5 +1,6 @@ import os import re +from collections import OrderedDict import dateutil # type: ignore[import-untyped] from packaging.requirements import Requirement @@ -46,6 +47,42 @@ def is_simple_case(self, req): return False +class ListExtendingOrderedDict(OrderedDict): + """An OrderedDict subclass that aggregates list values for duplicate keys. + + This class extends OrderedDict to provide special handling for list values. + When a list value is assigned to an existing key, the new list is extended + onto the existing list instead of replacing it. + + In the context of configparser, this allows for accumulating multiple values + from different sections or repeated keys, such as in multiline requirements. + + Examples: + >>> d = MultiOrderedDict() + >>> d['key'] = [1, 2] + >>> d['key'] = [3, 4] + >>> d['key'] + [1, 2, 3, 4] + >>> d['other'] = 'value' + >>> d['other'] = 'new_value' # Non-list values behave normally + >>> d['other'] + 'new_value' + """ + + def __setitem__(self, key, value): + """Sets the value for the given key, extending lists if the key exists. + + Args: + key: The dictionary key. + value: The value to set. If this is a list and the key already exists, + the list will be extended to the existing value instead of replacing it. + """ + if isinstance(value, list) and key in self: + self[key].extend(value) + else: + super().__setitem__(key, value) + + def touch_file(path: str, fileinfo: dict): 'upload-time: 2025-05-28T18:46:29.349478Z' time_str = fileinfo.get('upload-time')