python (3.11.7)
       1  """Exceptions used throughout package.
       2  
       3  This module MUST NOT try to import from anything within `pip._internal` to
       4  operate. This is expected to be importable from any/all files within the
       5  subpackage and, thus, should not depend on them.
       6  """
       7  
       8  import configparser
       9  import contextlib
      10  import locale
      11  import logging
      12  import pathlib
      13  import re
      14  import sys
      15  from itertools import chain, groupby, repeat
      16  from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Union
      17  
      18  from pip._vendor.requests.models import Request, Response
      19  from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
      20  from pip._vendor.rich.markup import escape
      21  from pip._vendor.rich.text import Text
      22  
      23  if TYPE_CHECKING:
      24      from hashlib import _Hash
      25      from typing import Literal
      26  
      27      from pip._internal.metadata import BaseDistribution
      28      from pip._internal.req.req_install import InstallRequirement
      29  
      30  logger = logging.getLogger(__name__)
      31  
      32  
      33  #
      34  # Scaffolding
      35  #
      36  def _is_kebab_case(s: str) -> bool:
      37      return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
      38  
      39  
      40  def _prefix_with_indent(
      41      s: Union[Text, str],
      42      console: Console,
      43      *,
      44      prefix: str,
      45      indent: str,
      46  ) -> Text:
      47      if isinstance(s, Text):
      48          text = s
      49      else:
      50          text = console.render_str(s)
      51  
      52      return console.render_str(prefix, overflow="ignore") + console.render_str(
      53          f"\n{indent}", overflow="ignore"
      54      ).join(text.split(allow_blank=True))
      55  
      56  
      57  class ESC[4;38;5;81mPipError(ESC[4;38;5;149mException):
      58      """The base pip error."""
      59  
      60  
      61  class ESC[4;38;5;81mDiagnosticPipError(ESC[4;38;5;149mPipError):
      62      """An error, that presents diagnostic information to the user.
      63  
      64      This contains a bunch of logic, to enable pretty presentation of our error
      65      messages. Each error gets a unique reference. Each error can also include
      66      additional context, a hint and/or a note -- which are presented with the
      67      main error message in a consistent style.
      68  
      69      This is adapted from the error output styling in `sphinx-theme-builder`.
      70      """
      71  
      72      reference: str
      73  
      74      def __init__(
      75          self,
      76          *,
      77          kind: 'Literal["error", "warning"]' = "error",
      78          reference: Optional[str] = None,
      79          message: Union[str, Text],
      80          context: Optional[Union[str, Text]],
      81          hint_stmt: Optional[Union[str, Text]],
      82          note_stmt: Optional[Union[str, Text]] = None,
      83          link: Optional[str] = None,
      84      ) -> None:
      85          # Ensure a proper reference is provided.
      86          if reference is None:
      87              assert hasattr(self, "reference"), "error reference not provided!"
      88              reference = self.reference
      89          assert _is_kebab_case(reference), "error reference must be kebab-case!"
      90  
      91          self.kind = kind
      92          self.reference = reference
      93  
      94          self.message = message
      95          self.context = context
      96  
      97          self.note_stmt = note_stmt
      98          self.hint_stmt = hint_stmt
      99  
     100          self.link = link
     101  
     102          super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
     103  
     104      def __repr__(self) -> str:
     105          return (
     106              f"<{self.__class__.__name__}("
     107              f"reference={self.reference!r}, "
     108              f"message={self.message!r}, "
     109              f"context={self.context!r}, "
     110              f"note_stmt={self.note_stmt!r}, "
     111              f"hint_stmt={self.hint_stmt!r}"
     112              ")>"
     113          )
     114  
     115      def __rich_console__(
     116          self,
     117          console: Console,
     118          options: ConsoleOptions,
     119      ) -> RenderResult:
     120          colour = "red" if self.kind == "error" else "yellow"
     121  
     122          yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
     123          yield ""
     124  
     125          if not options.ascii_only:
     126              # Present the main message, with relevant context indented.
     127              if self.context is not None:
     128                  yield _prefix_with_indent(
     129                      self.message,
     130                      console,
     131                      prefix=f"[{colour}]×[/] ",
     132                      indent=f"[{colour}]│[/] ",
     133                  )
     134                  yield _prefix_with_indent(
     135                      self.context,
     136                      console,
     137                      prefix=f"[{colour}]╰─>[/] ",
     138                      indent=f"[{colour}]   [/] ",
     139                  )
     140              else:
     141                  yield _prefix_with_indent(
     142                      self.message,
     143                      console,
     144                      prefix="[red]×[/] ",
     145                      indent="  ",
     146                  )
     147          else:
     148              yield self.message
     149              if self.context is not None:
     150                  yield ""
     151                  yield self.context
     152  
     153          if self.note_stmt is not None or self.hint_stmt is not None:
     154              yield ""
     155  
     156          if self.note_stmt is not None:
     157              yield _prefix_with_indent(
     158                  self.note_stmt,
     159                  console,
     160                  prefix="[magenta bold]note[/]: ",
     161                  indent="      ",
     162              )
     163          if self.hint_stmt is not None:
     164              yield _prefix_with_indent(
     165                  self.hint_stmt,
     166                  console,
     167                  prefix="[cyan bold]hint[/]: ",
     168                  indent="      ",
     169              )
     170  
     171          if self.link is not None:
     172              yield ""
     173              yield f"Link: {self.link}"
     174  
     175  
     176  #
     177  # Actual Errors
     178  #
     179  class ESC[4;38;5;81mConfigurationError(ESC[4;38;5;149mPipError):
     180      """General exception in configuration"""
     181  
     182  
     183  class ESC[4;38;5;81mInstallationError(ESC[4;38;5;149mPipError):
     184      """General exception during installation"""
     185  
     186  
     187  class ESC[4;38;5;81mUninstallationError(ESC[4;38;5;149mPipError):
     188      """General exception during uninstallation"""
     189  
     190  
     191  class ESC[4;38;5;81mMissingPyProjectBuildRequires(ESC[4;38;5;149mDiagnosticPipError):
     192      """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
     193  
     194      reference = "missing-pyproject-build-system-requires"
     195  
     196      def __init__(self, *, package: str) -> None:
     197          super().__init__(
     198              message=f"Can not process {escape(package)}",
     199              context=Text(
     200                  "This package has an invalid pyproject.toml file.\n"
     201                  "The [build-system] table is missing the mandatory `requires` key."
     202              ),
     203              note_stmt="This is an issue with the package mentioned above, not pip.",
     204              hint_stmt=Text("See PEP 518 for the detailed specification."),
     205          )
     206  
     207  
     208  class ESC[4;38;5;81mInvalidPyProjectBuildRequires(ESC[4;38;5;149mDiagnosticPipError):
     209      """Raised when pyproject.toml an invalid `build-system.requires`."""
     210  
     211      reference = "invalid-pyproject-build-system-requires"
     212  
     213      def __init__(self, *, package: str, reason: str) -> None:
     214          super().__init__(
     215              message=f"Can not process {escape(package)}",
     216              context=Text(
     217                  "This package has an invalid `build-system.requires` key in "
     218                  f"pyproject.toml.\n{reason}"
     219              ),
     220              note_stmt="This is an issue with the package mentioned above, not pip.",
     221              hint_stmt=Text("See PEP 518 for the detailed specification."),
     222          )
     223  
     224  
     225  class ESC[4;38;5;81mNoneMetadataError(ESC[4;38;5;149mPipError):
     226      """Raised when accessing a Distribution's "METADATA" or "PKG-INFO".
     227  
     228      This signifies an inconsistency, when the Distribution claims to have
     229      the metadata file (if not, raise ``FileNotFoundError`` instead), but is
     230      not actually able to produce its content. This may be due to permission
     231      errors.
     232      """
     233  
     234      def __init__(
     235          self,
     236          dist: "BaseDistribution",
     237          metadata_name: str,
     238      ) -> None:
     239          """
     240          :param dist: A Distribution object.
     241          :param metadata_name: The name of the metadata being accessed
     242              (can be "METADATA" or "PKG-INFO").
     243          """
     244          self.dist = dist
     245          self.metadata_name = metadata_name
     246  
     247      def __str__(self) -> str:
     248          # Use `dist` in the error message because its stringification
     249          # includes more information, like the version and location.
     250          return "None {} metadata found for distribution: {}".format(
     251              self.metadata_name,
     252              self.dist,
     253          )
     254  
     255  
     256  class ESC[4;38;5;81mUserInstallationInvalid(ESC[4;38;5;149mInstallationError):
     257      """A --user install is requested on an environment without user site."""
     258  
     259      def __str__(self) -> str:
     260          return "User base directory is not specified"
     261  
     262  
     263  class ESC[4;38;5;81mInvalidSchemeCombination(ESC[4;38;5;149mInstallationError):
     264      def __str__(self) -> str:
     265          before = ", ".join(str(a) for a in self.args[:-1])
     266          return f"Cannot set {before} and {self.args[-1]} together"
     267  
     268  
     269  class ESC[4;38;5;81mDistributionNotFound(ESC[4;38;5;149mInstallationError):
     270      """Raised when a distribution cannot be found to satisfy a requirement"""
     271  
     272  
     273  class ESC[4;38;5;81mRequirementsFileParseError(ESC[4;38;5;149mInstallationError):
     274      """Raised when a general error occurs parsing a requirements file line."""
     275  
     276  
     277  class ESC[4;38;5;81mBestVersionAlreadyInstalled(ESC[4;38;5;149mPipError):
     278      """Raised when the most up-to-date version of a package is already
     279      installed."""
     280  
     281  
     282  class ESC[4;38;5;81mBadCommand(ESC[4;38;5;149mPipError):
     283      """Raised when virtualenv or a command is not found"""
     284  
     285  
     286  class ESC[4;38;5;81mCommandError(ESC[4;38;5;149mPipError):
     287      """Raised when there is an error in command-line arguments"""
     288  
     289  
     290  class ESC[4;38;5;81mPreviousBuildDirError(ESC[4;38;5;149mPipError):
     291      """Raised when there's a previous conflicting build directory"""
     292  
     293  
     294  class ESC[4;38;5;81mNetworkConnectionError(ESC[4;38;5;149mPipError):
     295      """HTTP connection error"""
     296  
     297      def __init__(
     298          self,
     299          error_msg: str,
     300          response: Optional[Response] = None,
     301          request: Optional[Request] = None,
     302      ) -> None:
     303          """
     304          Initialize NetworkConnectionError with  `request` and `response`
     305          objects.
     306          """
     307          self.response = response
     308          self.request = request
     309          self.error_msg = error_msg
     310          if (
     311              self.response is not None
     312              and not self.request
     313              and hasattr(response, "request")
     314          ):
     315              self.request = self.response.request
     316          super().__init__(error_msg, response, request)
     317  
     318      def __str__(self) -> str:
     319          return str(self.error_msg)
     320  
     321  
     322  class ESC[4;38;5;81mInvalidWheelFilename(ESC[4;38;5;149mInstallationError):
     323      """Invalid wheel filename."""
     324  
     325  
     326  class ESC[4;38;5;81mUnsupportedWheel(ESC[4;38;5;149mInstallationError):
     327      """Unsupported wheel."""
     328  
     329  
     330  class ESC[4;38;5;81mInvalidWheel(ESC[4;38;5;149mInstallationError):
     331      """Invalid (e.g. corrupt) wheel."""
     332  
     333      def __init__(self, location: str, name: str):
     334          self.location = location
     335          self.name = name
     336  
     337      def __str__(self) -> str:
     338          return f"Wheel '{self.name}' located at {self.location} is invalid."
     339  
     340  
     341  class ESC[4;38;5;81mMetadataInconsistent(ESC[4;38;5;149mInstallationError):
     342      """Built metadata contains inconsistent information.
     343  
     344      This is raised when the metadata contains values (e.g. name and version)
     345      that do not match the information previously obtained from sdist filename,
     346      user-supplied ``#egg=`` value, or an install requirement name.
     347      """
     348  
     349      def __init__(
     350          self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
     351      ) -> None:
     352          self.ireq = ireq
     353          self.field = field
     354          self.f_val = f_val
     355          self.m_val = m_val
     356  
     357      def __str__(self) -> str:
     358          return (
     359              f"Requested {self.ireq} has inconsistent {self.field}: "
     360              f"expected {self.f_val!r}, but metadata has {self.m_val!r}"
     361          )
     362  
     363  
     364  class ESC[4;38;5;81mInstallationSubprocessError(ESC[4;38;5;149mDiagnosticPipError, ESC[4;38;5;149mInstallationError):
     365      """A subprocess call failed."""
     366  
     367      reference = "subprocess-exited-with-error"
     368  
     369      def __init__(
     370          self,
     371          *,
     372          command_description: str,
     373          exit_code: int,
     374          output_lines: Optional[List[str]],
     375      ) -> None:
     376          if output_lines is None:
     377              output_prompt = Text("See above for output.")
     378          else:
     379              output_prompt = (
     380                  Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
     381                  + Text("".join(output_lines))
     382                  + Text.from_markup(R"[red]\[end of output][/]")
     383              )
     384  
     385          super().__init__(
     386              message=(
     387                  f"[green]{escape(command_description)}[/] did not run successfully.\n"
     388                  f"exit code: {exit_code}"
     389              ),
     390              context=output_prompt,
     391              hint_stmt=None,
     392              note_stmt=(
     393                  "This error originates from a subprocess, and is likely not a "
     394                  "problem with pip."
     395              ),
     396          )
     397  
     398          self.command_description = command_description
     399          self.exit_code = exit_code
     400  
     401      def __str__(self) -> str:
     402          return f"{self.command_description} exited with {self.exit_code}"
     403  
     404  
     405  class ESC[4;38;5;81mMetadataGenerationFailed(ESC[4;38;5;149mInstallationSubprocessError, ESC[4;38;5;149mInstallationError):
     406      reference = "metadata-generation-failed"
     407  
     408      def __init__(
     409          self,
     410          *,
     411          package_details: str,
     412      ) -> None:
     413          super(InstallationSubprocessError, self).__init__(
     414              message="Encountered error while generating package metadata.",
     415              context=escape(package_details),
     416              hint_stmt="See above for details.",
     417              note_stmt="This is an issue with the package mentioned above, not pip.",
     418          )
     419  
     420      def __str__(self) -> str:
     421          return "metadata generation failed"
     422  
     423  
     424  class ESC[4;38;5;81mHashErrors(ESC[4;38;5;149mInstallationError):
     425      """Multiple HashError instances rolled into one for reporting"""
     426  
     427      def __init__(self) -> None:
     428          self.errors: List["HashError"] = []
     429  
     430      def append(self, error: "HashError") -> None:
     431          self.errors.append(error)
     432  
     433      def __str__(self) -> str:
     434          lines = []
     435          self.errors.sort(key=lambda e: e.order)
     436          for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
     437              lines.append(cls.head)
     438              lines.extend(e.body() for e in errors_of_cls)
     439          if lines:
     440              return "\n".join(lines)
     441          return ""
     442  
     443      def __bool__(self) -> bool:
     444          return bool(self.errors)
     445  
     446  
     447  class ESC[4;38;5;81mHashError(ESC[4;38;5;149mInstallationError):
     448      """
     449      A failure to verify a package against known-good hashes
     450  
     451      :cvar order: An int sorting hash exception classes by difficulty of
     452          recovery (lower being harder), so the user doesn't bother fretting
     453          about unpinned packages when he has deeper issues, like VCS
     454          dependencies, to deal with. Also keeps error reports in a
     455          deterministic order.
     456      :cvar head: A section heading for display above potentially many
     457          exceptions of this kind
     458      :ivar req: The InstallRequirement that triggered this error. This is
     459          pasted on after the exception is instantiated, because it's not
     460          typically available earlier.
     461  
     462      """
     463  
     464      req: Optional["InstallRequirement"] = None
     465      head = ""
     466      order: int = -1
     467  
     468      def body(self) -> str:
     469          """Return a summary of me for display under the heading.
     470  
     471          This default implementation simply prints a description of the
     472          triggering requirement.
     473  
     474          :param req: The InstallRequirement that provoked this error, with
     475              its link already populated by the resolver's _populate_link().
     476  
     477          """
     478          return f"    {self._requirement_name()}"
     479  
     480      def __str__(self) -> str:
     481          return f"{self.head}\n{self.body()}"
     482  
     483      def _requirement_name(self) -> str:
     484          """Return a description of the requirement that triggered me.
     485  
     486          This default implementation returns long description of the req, with
     487          line numbers
     488  
     489          """
     490          return str(self.req) if self.req else "unknown package"
     491  
     492  
     493  class ESC[4;38;5;81mVcsHashUnsupported(ESC[4;38;5;149mHashError):
     494      """A hash was provided for a version-control-system-based requirement, but
     495      we don't have a method for hashing those."""
     496  
     497      order = 0
     498      head = (
     499          "Can't verify hashes for these requirements because we don't "
     500          "have a way to hash version control repositories:"
     501      )
     502  
     503  
     504  class ESC[4;38;5;81mDirectoryUrlHashUnsupported(ESC[4;38;5;149mHashError):
     505      """A hash was provided for a version-control-system-based requirement, but
     506      we don't have a method for hashing those."""
     507  
     508      order = 1
     509      head = (
     510          "Can't verify hashes for these file:// requirements because they "
     511          "point to directories:"
     512      )
     513  
     514  
     515  class ESC[4;38;5;81mHashMissing(ESC[4;38;5;149mHashError):
     516      """A hash was needed for a requirement but is absent."""
     517  
     518      order = 2
     519      head = (
     520          "Hashes are required in --require-hashes mode, but they are "
     521          "missing from some requirements. Here is a list of those "
     522          "requirements along with the hashes their downloaded archives "
     523          "actually had. Add lines like these to your requirements files to "
     524          "prevent tampering. (If you did not enable --require-hashes "
     525          "manually, note that it turns on automatically when any package "
     526          "has a hash.)"
     527      )
     528  
     529      def __init__(self, gotten_hash: str) -> None:
     530          """
     531          :param gotten_hash: The hash of the (possibly malicious) archive we
     532              just downloaded
     533          """
     534          self.gotten_hash = gotten_hash
     535  
     536      def body(self) -> str:
     537          # Dodge circular import.
     538          from pip._internal.utils.hashes import FAVORITE_HASH
     539  
     540          package = None
     541          if self.req:
     542              # In the case of URL-based requirements, display the original URL
     543              # seen in the requirements file rather than the package name,
     544              # so the output can be directly copied into the requirements file.
     545              package = (
     546                  self.req.original_link
     547                  if self.req.is_direct
     548                  # In case someone feeds something downright stupid
     549                  # to InstallRequirement's constructor.
     550                  else getattr(self.req, "req", None)
     551              )
     552          return "    {} --hash={}:{}".format(
     553              package or "unknown package", FAVORITE_HASH, self.gotten_hash
     554          )
     555  
     556  
     557  class ESC[4;38;5;81mHashUnpinned(ESC[4;38;5;149mHashError):
     558      """A requirement had a hash specified but was not pinned to a specific
     559      version."""
     560  
     561      order = 3
     562      head = (
     563          "In --require-hashes mode, all requirements must have their "
     564          "versions pinned with ==. These do not:"
     565      )
     566  
     567  
     568  class ESC[4;38;5;81mHashMismatch(ESC[4;38;5;149mHashError):
     569      """
     570      Distribution file hash values don't match.
     571  
     572      :ivar package_name: The name of the package that triggered the hash
     573          mismatch. Feel free to write to this after the exception is raise to
     574          improve its error message.
     575  
     576      """
     577  
     578      order = 4
     579      head = (
     580          "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "
     581          "FILE. If you have updated the package versions, please update "
     582          "the hashes. Otherwise, examine the package contents carefully; "
     583          "someone may have tampered with them."
     584      )
     585  
     586      def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
     587          """
     588          :param allowed: A dict of algorithm names pointing to lists of allowed
     589              hex digests
     590          :param gots: A dict of algorithm names pointing to hashes we
     591              actually got from the files under suspicion
     592          """
     593          self.allowed = allowed
     594          self.gots = gots
     595  
     596      def body(self) -> str:
     597          return "    {}:\n{}".format(self._requirement_name(), self._hash_comparison())
     598  
     599      def _hash_comparison(self) -> str:
     600          """
     601          Return a comparison of actual and expected hash values.
     602  
     603          Example::
     604  
     605                 Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
     606                              or 123451234512345123451234512345123451234512345
     607                      Got        bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
     608  
     609          """
     610  
     611          def hash_then_or(hash_name: str) -> "chain[str]":
     612              # For now, all the decent hashes have 6-char names, so we can get
     613              # away with hard-coding space literals.
     614              return chain([hash_name], repeat("    or"))
     615  
     616          lines: List[str] = []
     617          for hash_name, expecteds in self.allowed.items():
     618              prefix = hash_then_or(hash_name)
     619              lines.extend(
     620                  ("        Expected {} {}".format(next(prefix), e)) for e in expecteds
     621              )
     622              lines.append(
     623                  "             Got        {}\n".format(self.gots[hash_name].hexdigest())
     624              )
     625          return "\n".join(lines)
     626  
     627  
     628  class ESC[4;38;5;81mUnsupportedPythonVersion(ESC[4;38;5;149mInstallationError):
     629      """Unsupported python version according to Requires-Python package
     630      metadata."""
     631  
     632  
     633  class ESC[4;38;5;81mConfigurationFileCouldNotBeLoaded(ESC[4;38;5;149mConfigurationError):
     634      """When there are errors while loading a configuration file"""
     635  
     636      def __init__(
     637          self,
     638          reason: str = "could not be loaded",
     639          fname: Optional[str] = None,
     640          error: Optional[configparser.Error] = None,
     641      ) -> None:
     642          super().__init__(error)
     643          self.reason = reason
     644          self.fname = fname
     645          self.error = error
     646  
     647      def __str__(self) -> str:
     648          if self.fname is not None:
     649              message_part = f" in {self.fname}."
     650          else:
     651              assert self.error is not None
     652              message_part = f".\n{self.error}\n"
     653          return f"Configuration file {self.reason}{message_part}"
     654  
     655  
     656  _DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\
     657  The Python environment under {sys.prefix} is managed externally, and may not be
     658  manipulated by the user. Please use specific tooling from the distributor of
     659  the Python installation to interact with this environment instead.
     660  """
     661  
     662  
     663  class ESC[4;38;5;81mExternallyManagedEnvironment(ESC[4;38;5;149mDiagnosticPipError):
     664      """The current environment is externally managed.
     665  
     666      This is raised when the current environment is externally managed, as
     667      defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked
     668      and displayed when the error is bubbled up to the user.
     669  
     670      :param error: The error message read from ``EXTERNALLY-MANAGED``.
     671      """
     672  
     673      reference = "externally-managed-environment"
     674  
     675      def __init__(self, error: Optional[str]) -> None:
     676          if error is None:
     677              context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
     678          else:
     679              context = Text(error)
     680          super().__init__(
     681              message="This environment is externally managed",
     682              context=context,
     683              note_stmt=(
     684                  "If you believe this is a mistake, please contact your "
     685                  "Python installation or OS distribution provider. "
     686                  "You can override this, at the risk of breaking your Python "
     687                  "installation or OS, by passing --break-system-packages."
     688              ),
     689              hint_stmt=Text("See PEP 668 for the detailed specification."),
     690          )
     691  
     692      @staticmethod
     693      def _iter_externally_managed_error_keys() -> Iterator[str]:
     694          # LC_MESSAGES is in POSIX, but not the C standard. The most common
     695          # platform that does not implement this category is Windows, where
     696          # using other categories for console message localization is equally
     697          # unreliable, so we fall back to the locale-less vendor message. This
     698          # can always be re-evaluated when a vendor proposes a new alternative.
     699          try:
     700              category = locale.LC_MESSAGES
     701          except AttributeError:
     702              lang: Optional[str] = None
     703          else:
     704              lang, _ = locale.getlocale(category)
     705          if lang is not None:
     706              yield f"Error-{lang}"
     707              for sep in ("-", "_"):
     708                  before, found, _ = lang.partition(sep)
     709                  if not found:
     710                      continue
     711                  yield f"Error-{before}"
     712          yield "Error"
     713  
     714      @classmethod
     715      def from_config(
     716          cls,
     717          config: Union[pathlib.Path, str],
     718      ) -> "ExternallyManagedEnvironment":
     719          parser = configparser.ConfigParser(interpolation=None)
     720          try:
     721              parser.read(config, encoding="utf-8")
     722              section = parser["externally-managed"]
     723              for key in cls._iter_externally_managed_error_keys():
     724                  with contextlib.suppress(KeyError):
     725                      return cls(section[key])
     726          except KeyError:
     727              pass
     728          except (OSError, UnicodeDecodeError, configparser.ParsingError):
     729              from pip._internal.utils._log import VERBOSE
     730  
     731              exc_info = logger.isEnabledFor(VERBOSE)
     732              logger.warning("Failed to read %s", config, exc_info=exc_info)
     733          return cls(None)