{"id":"f98df96fce4ab48a","repo":"pypa/pip","slug":"dynamic-configparser-error","errorCode":null,"errorMessage":"<dynamic: configparser error>","messagePattern":"<dynamic: configparser error>","errorType":"exception","errorClass":"ConfigurationFileCouldNotBeLoaded","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/configuration.py","lineNumber":306,"sourceCode":"    def _construct_parser(self, fname: str) -> RawConfigParser:\n        parser = configparser.RawConfigParser()\n        # If there is no such file, don't bother reading it but create the\n        # parser anyway, to hold the data.\n        # Doing this is useful when modifying and saving files, where we don't\n        # need to construct a parser.\n        if os.path.exists(fname):\n            locale_encoding = get_locale_encoding()\n            try:\n                parser.read(fname, encoding=locale_encoding)\n            except UnicodeDecodeError:\n                # See https://github.com/pypa/pip/issues/4963\n                raise ConfigurationFileCouldNotBeLoaded(\n                    reason=f\"contains invalid {locale_encoding} characters\",\n                    fname=fname,\n                )\n            except configparser.Error as error:\n                # See https://github.com/pypa/pip/issues/4893\n                raise ConfigurationFileCouldNotBeLoaded(error=error)\n        return parser\n\n    def _load_environment_vars(self) -> None:\n        \"\"\"Loads configuration from environment variables\"\"\"\n        self._config[kinds.ENV_VAR].setdefault(\":env:\", {})\n        self._config[kinds.ENV_VAR][\":env:\"].update(\n            self._normalized_keys(\":env:\", self.get_environ_vars())\n        )\n\n    def _normalized_keys(\n        self, section: str, items: Iterable[tuple[str, Any]]\n    ) -> dict[str, Any]:\n        \"\"\"Normalizes items to construct a dictionary with normalized keys.\n\n        This routine is where the names become keys and are made the same\n        regardless of source - configuration files or environment.\n        \"\"\"\n        normalized = {}","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/configuration.py#L288-L324","documentation":"Raised when pip's config file parser (configparser.RawConfigParser) hits a structural error while reading a pip config / requirements configuration file (e.g. duplicate options, malformed sections, or missing section headers). It is wrapped in ConfigurationFileCouldNotBeLoaded so the user gets a friendly message naming the offending file rather than a raw configparser traceback. The underlying configparser.Error (DuplicateOptionError, MissingSectionHeaderError, DuplicateSectionError, etc.) is stored in .error.","triggerScenarios":"Hit during configuration loading (Configuration.load() -> _construct_parser) when the file exists but contains INI syntax that configparser rejects: a line before any [section], a duplicate key/section, or an option assignment parser cannot parse. PIP_CONFIG_FILE pointing at a hand-edited file is the most common trigger.","commonSituations":"Editing pip.conf / pip.ini manually and introducing a typo; copy-pasting a pip config that uses 'key=value' without a leading [global]/[install] section; switching from a requirements.txt-style flat file into pip.conf without adding sections; duplicate 'index-url' / 'find-links' keys under the same section.","solutions":["Open the config file named in the error and fix the reported configparser issue (add the missing [section] header, remove the duplicate key, or correct syntax).","Validate with 'python -m configparser -c \"import configparser; c=configparser.ConfigParser(); c.read(\\\"<file>\\\")\"' or pip config debug to see which file fails.","If unsure which file is being loaded, run with PIP_CONFIG_FILE set to a known-good file to isolate the source.","As a temporary workaround, run with --no-input and an explicit --config-file, or --isolated to skip config loading entirely."],"exampleFix":"; before\nindex-url = https://pypi.org/simple\n[global]\ntrusted-host = pypi.org\n\n; after (section header must come first)\n[global]\nindex-url = https://pypi.org/simple\ntrusted-host = pypi.org","handlingStrategy":"try-catch","validationCode":"import configparser, sys\np = configparser.RawConfigParser()\ntry:\n    p.read(path, encoding='utf-8')\nexcept configparser.Error as e:\n    print(f'config invalid: {e}'); sys.exit(1)\nprint('config OK')","typeGuard":"def is_valid_config_file(path: str) -> bool:\n    import configparser\n    p = configparser.RawConfigParser()\n    try:\n        p.read(path, encoding='utf-8')\n        return True\n    except configparser.Error:\n        return False","tryCatchPattern":"from pip._internal.exceptions import ConfigurationFileCouldNotBeLoaded\ntry:\n    configuration.load()\nexcept ConfigurationFileCouldNotBeLoaded as e:\n    # e.fname, e.reason, e.error (configparser.Error)\n    log.error('Config %s failed: %s', e.fname, e.reason)","preventionTips":["Validate pip.conf with 'pip config debug' after edits.","Keep config files under version control and review diffs.","Never put option lines before a [section] header.","Use PIP_CONFIG_FILE pointing at a tested file in CI."],"tags":["config","configparser","pip-conf","ini-parse"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}