We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
`poetry check` does not consider the package source
<!--
    Hi there! Thank you for submitting a feature request!

    Before you submit your issue, please review and follow the instructions at
    https://python-poetry.org/docs/contributing/#suggesting-enhancements.
-->

<!-- All the below steps should be completed before submitting your issue. Checked checkbox should look like this: [x] -->
- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] I have searched the [FAQ](https://python-poetry.org/docs/faq/) and general [documentation](https://python-poetry.org/docs/) and believe that my question is not already covered.

## Feature Request
<!-- Now feel free to write your issue, and please be as descriptive as possible! -->
<!-- Thanks again 🙌 ❤ -->

It would be excellent if `poetry check` could encompass common errors? when defining package sources.

### Source is undefined
- if there is a package point to not existing source

```yaml
[tool.poetry]
name = "PythonProjects"
version = "2.0.0"
homepage = "https://github.com/roghu/py3_projects"
license = "MIT"
readme = "README.md"
authors = ["Dependabot <support@dependabot.com>"]
description = "Various small python projects."

[tool.poetry.dependencies]
python = "^3.7"
requests = { version = "2.18.0", source = "custom-1" }

[[tool.poetry.source]]
name = "custom"
url = "https://some.internal.registry.com/pypi/"
```

- poetry install will failed directly

```
(pythonprojects-py3.11) ➜  dependabot-source-constraint git:(main) ✗ poetry install
Warning: In a future version of Poetry, PyPI will be disabled automatically if at least one custom primary source is configured. In order to avoid a breaking change and make your pyproject.toml forward compatible, add PyPI explicitly via 'poetry source add pypi'. By the way, this has the advantage that you can set the priority of PyPI as with any other source.
Updating dependencies
Resolving dependencies... (0.0s)

Repository "custom-1" does not exist.
```

### Package's source is not consistence 
- The behavior is undefined when a package is defined with different source configurations.

```yaml
[tool.poetry]
name = "PythonProjects"
version = "2.0.0"
homepage = "https://github.com/roghu/py3_projects"
license = "MIT"
readme = "README.md"
authors = ["Dependabot <support@dependabot.com>"]
description = "Various small python projects."


[tool.poetry.dependencies]
python = "^3.7"

requests = { version = "2.18.0", source = "custom-2" }
[tool.poetry.group.dev]
optional = true

[tool.poetry.group.dev.dependencies]
requests = { version = "2.18.0", extras=["dev"], source = "custom-3" }

[tool.poetry.group.test]
optional = true

[tool.poetry.group.test.dependencies]
requests = { version = "2.18.0", extras=["test"], source = "custom-1" }
 

[[tool.poetry.source]]
name = "custom-1"
url = "https://some.internal.registry-1.com/pypi/"

[[tool.poetry.source]]
name = "custom-2"
url = "https://some.internal.registry-2.com/pypi/"
```

- Currently, Poetry selects only one source.
- It appears that Poetry selects the source that is not in the default group and appears last in the pyproject.toml file.

```
(pythonprojects-py3.11) ➜  dependabot-source-constraint git:(main) ✗ poetry install -vvv
Adding repository custom-1 (https://some.internal.registry-1.com/pypi) and setting it as primary
Adding repository custom-2 (https://some.internal.registry-2.com/pypi) and setting it as primary
Warning: In a future version of Poetry, PyPI will be disabled automatically if at least one custom primary source is configured. In order to avoid a breaking change and make your pyproject.toml forward compatible, add PyPI explicitly via 'poetry source add pypi'. By the way, this has the advantage that you can set the priority of PyPI as with any other source.
Using virtualenv: /Users/davidchen/repo/dependabot-source-constraint/.venv
Updating dependencies
Resolving dependencies...
   1: fact: pythonprojects is 2.0.0
   1: derived: pythonprojects
   1: fact: pythonprojects depends on requests (2.18.0)
   1: fact: pythonprojects depends on requests (2.18.0)
   1: fact: pythonprojects depends on requests (2.18.0)
   1: selecting pythonprojects (2.0.0)
   1: derived: requests[test] (==2.18.0)
   1: derived: requests[dev] (==2.18.0)
   1: derived: requests (==2.18.0)
[keyring.backend] Loading KWallet
[keyring.backend] Loading SecretService
[keyring.backend] Loading Windows
[keyring.backend] Loading chainer
[keyring.backend] Loading libsecret
[keyring.backend] Loading macOS
[keyring.backend] Loading Google Auth
Creating new session for some.internal.registry-1.com
[urllib3.connectionpool] Starting new HTTPS connection (1): some.internal.registry-1.com:443
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/poetry/console/commands/check.py`
Content:
```
1 from __future__ import annotations
2 
3 from typing import TYPE_CHECKING
4 
5 from cleo.helpers import option
6 
7 from poetry.console.commands.command import Command
8 
9 
10 if TYPE_CHECKING:
11     from pathlib import Path
12 
13 
14 class CheckCommand(Command):
15     name = "check"
16     description = (
17         "Validates the content of the <comment>pyproject.toml</> file and its"
18         " consistency with the poetry.lock file."
19     )
20 
21     options = [
22         option(
23             "lock",
24             None,
25             "Checks that <comment>poetry.lock</> exists for the current"
26             " version of <comment>pyproject.toml</>.",
27         ),
28     ]
29 
30     def _validate_classifiers(
31         self, project_classifiers: set[str]
32     ) -> tuple[list[str], list[str]]:
33         """Identify unrecognized and deprecated trove classifiers.
34 
35         A fully-qualified classifier is a string delimited by `` :: `` separators. To
36         make the error message more readable we need to have visual clues to
37         materialize the start and end of a classifier string. That way the user can
38         easily copy and paste it from the messages while reducing mistakes because of
39         extra spaces.
40 
41         We use ``!r`` (``repr()``) for classifiers and list of classifiers for
42         consistency. That way all strings will be rendered with the same kind of quotes
43         (i.e. simple tick: ``'``).
44         """
45         from trove_classifiers import classifiers
46         from trove_classifiers import deprecated_classifiers
47 
48         errors = []
49         warnings = []
50 
51         unrecognized = sorted(
52             project_classifiers - set(classifiers) - set(deprecated_classifiers)
53         )
54         # Allow "Private ::" classifiers as recommended on PyPI and the packaging guide
55         # to allow users to avoid accidentally publishing private packages to PyPI.
56         # https://pypi.org/classifiers/
57         unrecognized = [u for u in unrecognized if not u.startswith("Private ::")]
58         if unrecognized:
59             errors.append(f"Unrecognized classifiers: {unrecognized!r}.")
60 
61         deprecated = sorted(
62             project_classifiers.intersection(set(deprecated_classifiers))
63         )
64         if deprecated:
65             for old_classifier in deprecated:
66                 new_classifiers = deprecated_classifiers[old_classifier]
67                 if new_classifiers:
68                     message = (
69                         f"Deprecated classifier {old_classifier!r}. "
70                         f"Must be replaced by {new_classifiers!r}."
71                     )
72                 else:
73                     message = (
74                         f"Deprecated classifier {old_classifier!r}. Must be removed."
75                     )
76                 warnings.append(message)
77 
78         return errors, warnings
79 
80     def _validate_readme(self, readme: str | list[str], poetry_file: Path) -> list[str]:
81         """Check existence of referenced readme files"""
82 
83         readmes = [readme] if isinstance(readme, str) else readme
84 
85         errors = []
86         for name in readmes:
87             if not (poetry_file.parent / name).exists():
88                 errors.append(f"Declared README file does not exist: {name}")
89         return errors
90 
91     def handle(self) -> int:
92         from poetry.factory import Factory
93         from poetry.pyproject.toml import PyProjectTOML
94 
95         # Load poetry config and display errors, if any
96         poetry_file = self.poetry.file.path
97         config = PyProjectTOML(poetry_file).poetry_config
98         check_result = Factory.validate(config, strict=True)
99 
100         # Validate trove classifiers
101         project_classifiers = set(config.get("classifiers", []))
102         errors, warnings = self._validate_classifiers(project_classifiers)
103         check_result["errors"].extend(errors)
104         check_result["warnings"].extend(warnings)
105 
106         # Validate readme (files must exist)
107         if "readme" in config:
108             errors = self._validate_readme(config["readme"], poetry_file)
109             check_result["errors"].extend(errors)
110 
111         # Verify that lock file is consistent
112         if self.option("lock") and not self.poetry.locker.is_locked():
113             check_result["errors"] += ["poetry.lock was not found."]
114         if self.poetry.locker.is_locked() and not self.poetry.locker.is_fresh():
115             check_result["errors"] += [
116                 "poetry.lock is not consistent with pyproject.toml. Run `poetry"
117                 " lock [--no-update]` to fix it."
118             ]
119 
120         if not check_result["errors"] and not check_result["warnings"]:
121             self.info("All set!")
122 
123             return 0
124 
125         for error in check_result["errors"]:
126             self.line_error(f"<error>Error: {error}</error>")
127 
128         for error in check_result["warnings"]:
129             self.line_error(f"<warning>Warning: {error}</warning>")
130 
131         return 1
132
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/poetry/console/commands/check.py b/src/poetry/console/commands/check.py
--- a/src/poetry/console/commands/check.py
+++ b/src/poetry/console/commands/check.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 from typing import TYPE_CHECKING
+from typing import Any
 
 from cleo.helpers import option
 
@@ -88,6 +89,37 @@
                 errors.append(f"Declared README file does not exist: {name}")
         return errors
 
+    def _validate_dependencies_source(self, config: dict[str, Any]) -> list[str]:
+        """Check dependencies's source are valid"""
+        sources = {k["name"] for k in config.get("source", [])}
+
+        dependency_declarations: list[
+            dict[str, str | dict[str, str] | list[dict[str, str]]]
+        ] = []
+        # scan dependencies and group dependencies settings in pyproject.toml
+        if "dependencies" in config:
+            dependency_declarations.append(config["dependencies"])
+
+        for group in config.get("group", {}).values():
+            if "dependencies" in group:
+                dependency_declarations.append(group["dependencies"])
+
+        all_referenced_sources: set[str] = set()
+
+        for dependency_declaration in dependency_declarations:
+            for declaration in dependency_declaration.values():
+                if isinstance(declaration, list):
+                    for item in declaration:
+                        if "source" in item:
+                            all_referenced_sources.add(item["source"])
+                elif isinstance(declaration, dict) and "source" in declaration:
+                    all_referenced_sources.add(declaration["source"])
+
+        return [
+            f'Invalid source "{source}" referenced in dependencies.'
+            for source in sorted(all_referenced_sources - sources)
+        ]
+
     def handle(self) -> int:
         from poetry.factory import Factory
         from poetry.pyproject.toml import PyProjectTOML
@@ -108,6 +140,8 @@
             errors = self._validate_readme(config["readme"], poetry_file)
             check_result["errors"].extend(errors)
 
+        check_result["errors"] += self._validate_dependencies_source(config)
+
         # Verify that lock file is consistent
         if self.option("lock") and not self.poetry.locker.is_locked():
             check_result["errors"] += ["poetry.lock was not found."]

{"golden_diff": "diff --git a/src/poetry/console/commands/check.py b/src/poetry/console/commands/check.py\n--- a/src/poetry/console/commands/check.py\n+++ b/src/poetry/console/commands/check.py\n@@ -1,6 +1,7 @@\n from __future__ import annotations\n \n from typing import TYPE_CHECKING\n+from typing import Any\n \n from cleo.helpers import option\n \n@@ -88,6 +89,37 @@\n                 errors.append(f\"Declared README file does not exist: {name}\")\n         return errors\n \n+    def _validate_dependencies_source(self, config: dict[str, Any]) -> list[str]:\n+        \"\"\"Check dependencies's source are valid\"\"\"\n+        sources = {k[\"name\"] for k in config.get(\"source\", [])}\n+\n+        dependency_declarations: list[\n+            dict[str, str | dict[str, str] | list[dict[str, str]]]\n+        ] = []\n+        # scan dependencies and group dependencies settings in pyproject.toml\n+        if \"dependencies\" in config:\n+            dependency_declarations.append(config[\"dependencies\"])\n+\n+        for group in config.get(\"group\", {}).values():\n+            if \"dependencies\" in group:\n+                dependency_declarations.append(group[\"dependencies\"])\n+\n+        all_referenced_sources: set[str] = set()\n+\n+        for dependency_declaration in dependency_declarations:\n+            for declaration in dependency_declaration.values():\n+                if isinstance(declaration, list):\n+                    for item in declaration:\n+                        if \"source\" in item:\n+                            all_referenced_sources.add(item[\"source\"])\n+                elif isinstance(declaration, dict) and \"source\" in declaration:\n+                    all_referenced_sources.add(declaration[\"source\"])\n+\n+        return [\n+            f'Invalid source \"{source}\" referenced in dependencies.'\n+            for source in sorted(all_referenced_sources - sources)\n+        ]\n+\n     def handle(self) -> int:\n         from poetry.factory import Factory\n         from poetry.pyproject.toml import PyProjectTOML\n@@ -108,6 +140,8 @@\n             errors = self._validate_readme(config[\"readme\"], poetry_file)\n             check_result[\"errors\"].extend(errors)\n \n+        check_result[\"errors\"] += self._validate_dependencies_source(config)\n+\n         # Verify that lock file is consistent\n         if self.option(\"lock\") and not self.poetry.locker.is_locked():\n             check_result[\"errors\"] += [\"poetry.lock was not found.\"]\n", "issue": "`poetry check` does not consider the package source\n<!--\r\n    Hi there! Thank you for submitting a feature request!\r\n\r\n    Before you submit your issue, please review and follow the instructions at\r\n    https://python-poetry.org/docs/contributing/#suggesting-enhancements.\r\n-->\r\n\r\n<!-- All the below steps should be completed before submitting your issue. Checked checkbox should look like this: [x] -->\r\n- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.\r\n- [x] I have searched the [FAQ](https://python-poetry.org/docs/faq/) and general [documentation](https://python-poetry.org/docs/) and believe that my question is not already covered.\r\n\r\n## Feature Request\r\n<!-- Now feel free to write your issue, and please be as descriptive as possible! -->\r\n<!-- Thanks again \ud83d\ude4c \u2764 -->\r\n\r\nIt would be excellent if `poetry check` could encompass common errors? when defining package sources.\r\n\r\n### Source is undefined\r\n- if there is a package point to not existing source\r\n\r\n```yaml\r\n[tool.poetry]\r\nname = \"PythonProjects\"\r\nversion = \"2.0.0\"\r\nhomepage = \"https://github.com/roghu/py3_projects\"\r\nlicense = \"MIT\"\r\nreadme = \"README.md\"\r\nauthors = [\"Dependabot <support@dependabot.com>\"]\r\ndescription = \"Various small python projects.\"\r\n\r\n[tool.poetry.dependencies]\r\npython = \"^3.7\"\r\nrequests = { version = \"2.18.0\", source = \"custom-1\" }\r\n\r\n[[tool.poetry.source]]\r\nname = \"custom\"\r\nurl = \"https://some.internal.registry.com/pypi/\"\r\n```\r\n\r\n- poetry install will failed directly\r\n\r\n```\r\n(pythonprojects-py3.11) \u279c  dependabot-source-constraint git:(main) \u2717 poetry install\r\nWarning: In a future version of Poetry, PyPI will be disabled automatically if at least one custom primary source is configured. In order to avoid a breaking change and make your pyproject.toml forward compatible, add PyPI explicitly via 'poetry source add pypi'. By the way, this has the advantage that you can set the priority of PyPI as with any other source.\r\nUpdating dependencies\r\nResolving dependencies... (0.0s)\r\n\r\nRepository \"custom-1\" does not exist.\r\n```\r\n\r\n### Package's source is not consistence \r\n- The behavior is undefined when a package is defined with different source configurations.\r\n\r\n```yaml\r\n[tool.poetry]\r\nname = \"PythonProjects\"\r\nversion = \"2.0.0\"\r\nhomepage = \"https://github.com/roghu/py3_projects\"\r\nlicense = \"MIT\"\r\nreadme = \"README.md\"\r\nauthors = [\"Dependabot <support@dependabot.com>\"]\r\ndescription = \"Various small python projects.\"\r\n\r\n\r\n[tool.poetry.dependencies]\r\npython = \"^3.7\"\r\n\r\nrequests = { version = \"2.18.0\", source = \"custom-2\" }\r\n[tool.poetry.group.dev]\r\noptional = true\r\n\r\n[tool.poetry.group.dev.dependencies]\r\nrequests = { version = \"2.18.0\", extras=[\"dev\"], source = \"custom-3\" }\r\n\r\n[tool.poetry.group.test]\r\noptional = true\r\n\r\n[tool.poetry.group.test.dependencies]\r\nrequests = { version = \"2.18.0\", extras=[\"test\"], source = \"custom-1\" }\r\n \r\n\r\n[[tool.poetry.source]]\r\nname = \"custom-1\"\r\nurl = \"https://some.internal.registry-1.com/pypi/\"\r\n\r\n[[tool.poetry.source]]\r\nname = \"custom-2\"\r\nurl = \"https://some.internal.registry-2.com/pypi/\"\r\n```\r\n\r\n- Currently, Poetry selects only one source.\r\n- It appears that Poetry selects the source that is not in the default group and appears last in the pyproject.toml file.\r\n\r\n```\r\n(pythonprojects-py3.11) \u279c  dependabot-source-constraint git:(main) \u2717 poetry install -vvv\r\nAdding repository custom-1 (https://some.internal.registry-1.com/pypi) and setting it as primary\r\nAdding repository custom-2 (https://some.internal.registry-2.com/pypi) and setting it as primary\r\nWarning: In a future version of Poetry, PyPI will be disabled automatically if at least one custom primary source is configured. In order to avoid a breaking change and make your pyproject.toml forward compatible, add PyPI explicitly via 'poetry source add pypi'. By the way, this has the advantage that you can set the priority of PyPI as with any other source.\r\nUsing virtualenv: /Users/davidchen/repo/dependabot-source-constraint/.venv\r\nUpdating dependencies\r\nResolving dependencies...\r\n   1: fact: pythonprojects is 2.0.0\r\n   1: derived: pythonprojects\r\n   1: fact: pythonprojects depends on requests (2.18.0)\r\n   1: fact: pythonprojects depends on requests (2.18.0)\r\n   1: fact: pythonprojects depends on requests (2.18.0)\r\n   1: selecting pythonprojects (2.0.0)\r\n   1: derived: requests[test] (==2.18.0)\r\n   1: derived: requests[dev] (==2.18.0)\r\n   1: derived: requests (==2.18.0)\r\n[keyring.backend] Loading KWallet\r\n[keyring.backend] Loading SecretService\r\n[keyring.backend] Loading Windows\r\n[keyring.backend] Loading chainer\r\n[keyring.backend] Loading libsecret\r\n[keyring.backend] Loading macOS\r\n[keyring.backend] Loading Google Auth\r\nCreating new session for some.internal.registry-1.com\r\n[urllib3.connectionpool] Starting new HTTPS connection (1): some.internal.registry-1.com:443\r\n```\n", "before_files": [{"content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom cleo.helpers import option\n\nfrom poetry.console.commands.command import Command\n\n\nif TYPE_CHECKING:\n    from pathlib import Path\n\n\nclass CheckCommand(Command):\n    name = \"check\"\n    description = (\n        \"Validates the content of the <comment>pyproject.toml</> file and its\"\n        \" consistency with the poetry.lock file.\"\n    )\n\n    options = [\n        option(\n            \"lock\",\n            None,\n            \"Checks that <comment>poetry.lock</> exists for the current\"\n            \" version of <comment>pyproject.toml</>.\",\n        ),\n    ]\n\n    def _validate_classifiers(\n        self, project_classifiers: set[str]\n    ) -> tuple[list[str], list[str]]:\n        \"\"\"Identify unrecognized and deprecated trove classifiers.\n\n        A fully-qualified classifier is a string delimited by `` :: `` separators. To\n        make the error message more readable we need to have visual clues to\n        materialize the start and end of a classifier string. That way the user can\n        easily copy and paste it from the messages while reducing mistakes because of\n        extra spaces.\n\n        We use ``!r`` (``repr()``) for classifiers and list of classifiers for\n        consistency. That way all strings will be rendered with the same kind of quotes\n        (i.e. simple tick: ``'``).\n        \"\"\"\n        from trove_classifiers import classifiers\n        from trove_classifiers import deprecated_classifiers\n\n        errors = []\n        warnings = []\n\n        unrecognized = sorted(\n            project_classifiers - set(classifiers) - set(deprecated_classifiers)\n        )\n        # Allow \"Private ::\" classifiers as recommended on PyPI and the packaging guide\n        # to allow users to avoid accidentally publishing private packages to PyPI.\n        # https://pypi.org/classifiers/\n        unrecognized = [u for u in unrecognized if not u.startswith(\"Private ::\")]\n        if unrecognized:\n            errors.append(f\"Unrecognized classifiers: {unrecognized!r}.\")\n\n        deprecated = sorted(\n            project_classifiers.intersection(set(deprecated_classifiers))\n        )\n        if deprecated:\n            for old_classifier in deprecated:\n                new_classifiers = deprecated_classifiers[old_classifier]\n                if new_classifiers:\n                    message = (\n                        f\"Deprecated classifier {old_classifier!r}. \"\n                        f\"Must be replaced by {new_classifiers!r}.\"\n                    )\n                else:\n                    message = (\n                        f\"Deprecated classifier {old_classifier!r}. Must be removed.\"\n                    )\n                warnings.append(message)\n\n        return errors, warnings\n\n    def _validate_readme(self, readme: str | list[str], poetry_file: Path) -> list[str]:\n        \"\"\"Check existence of referenced readme files\"\"\"\n\n        readmes = [readme] if isinstance(readme, str) else readme\n\n        errors = []\n        for name in readmes:\n            if not (poetry_file.parent / name).exists():\n                errors.append(f\"Declared README file does not exist: {name}\")\n        return errors\n\n    def handle(self) -> int:\n        from poetry.factory import Factory\n        from poetry.pyproject.toml import PyProjectTOML\n\n        # Load poetry config and display errors, if any\n        poetry_file = self.poetry.file.path\n        config = PyProjectTOML(poetry_file).poetry_config\n        check_result = Factory.validate(config, strict=True)\n\n        # Validate trove classifiers\n        project_classifiers = set(config.get(\"classifiers\", []))\n        errors, warnings = self._validate_classifiers(project_classifiers)\n        check_result[\"errors\"].extend(errors)\n        check_result[\"warnings\"].extend(warnings)\n\n        # Validate readme (files must exist)\n        if \"readme\" in config:\n            errors = self._validate_readme(config[\"readme\"], poetry_file)\n            check_result[\"errors\"].extend(errors)\n\n        # Verify that lock file is consistent\n        if self.option(\"lock\") and not self.poetry.locker.is_locked():\n            check_result[\"errors\"] += [\"poetry.lock was not found.\"]\n        if self.poetry.locker.is_locked() and not self.poetry.locker.is_fresh():\n            check_result[\"errors\"] += [\n                \"poetry.lock is not consistent with pyproject.toml. Run `poetry\"\n                \" lock [--no-update]` to fix it.\"\n            ]\n\n        if not check_result[\"errors\"] and not check_result[\"warnings\"]:\n            self.info(\"All set!\")\n\n            return 0\n\n        for error in check_result[\"errors\"]:\n            self.line_error(f\"<error>Error: {error}</error>\")\n\n        for error in check_result[\"warnings\"]:\n            self.line_error(f\"<warning>Warning: {error}</warning>\")\n\n        return 1\n", "path": "src/poetry/console/commands/check.py"}], "after_files": [{"content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\nfrom typing import Any\n\nfrom cleo.helpers import option\n\nfrom poetry.console.commands.command import Command\n\n\nif TYPE_CHECKING:\n    from pathlib import Path\n\n\nclass CheckCommand(Command):\n    name = \"check\"\n    description = (\n        \"Validates the content of the <comment>pyproject.toml</> file and its\"\n        \" consistency with the poetry.lock file.\"\n    )\n\n    options = [\n        option(\n            \"lock\",\n            None,\n            \"Checks that <comment>poetry.lock</> exists for the current\"\n            \" version of <comment>pyproject.toml</>.\",\n        ),\n    ]\n\n    def _validate_classifiers(\n        self, project_classifiers: set[str]\n    ) -> tuple[list[str], list[str]]:\n        \"\"\"Identify unrecognized and deprecated trove classifiers.\n\n        A fully-qualified classifier is a string delimited by `` :: `` separators. To\n        make the error message more readable we need to have visual clues to\n        materialize the start and end of a classifier string. That way the user can\n        easily copy and paste it from the messages while reducing mistakes because of\n        extra spaces.\n\n        We use ``!r`` (``repr()``) for classifiers and list of classifiers for\n        consistency. That way all strings will be rendered with the same kind of quotes\n        (i.e. simple tick: ``'``).\n        \"\"\"\n        from trove_classifiers import classifiers\n        from trove_classifiers import deprecated_classifiers\n\n        errors = []\n        warnings = []\n\n        unrecognized = sorted(\n            project_classifiers - set(classifiers) - set(deprecated_classifiers)\n        )\n        # Allow \"Private ::\" classifiers as recommended on PyPI and the packaging guide\n        # to allow users to avoid accidentally publishing private packages to PyPI.\n        # https://pypi.org/classifiers/\n        unrecognized = [u for u in unrecognized if not u.startswith(\"Private ::\")]\n        if unrecognized:\n            errors.append(f\"Unrecognized classifiers: {unrecognized!r}.\")\n\n        deprecated = sorted(\n            project_classifiers.intersection(set(deprecated_classifiers))\n        )\n        if deprecated:\n            for old_classifier in deprecated:\n                new_classifiers = deprecated_classifiers[old_classifier]\n                if new_classifiers:\n                    message = (\n                        f\"Deprecated classifier {old_classifier!r}. \"\n                        f\"Must be replaced by {new_classifiers!r}.\"\n                    )\n                else:\n                    message = (\n                        f\"Deprecated classifier {old_classifier!r}. Must be removed.\"\n                    )\n                warnings.append(message)\n\n        return errors, warnings\n\n    def _validate_readme(self, readme: str | list[str], poetry_file: Path) -> list[str]:\n        \"\"\"Check existence of referenced readme files\"\"\"\n\n        readmes = [readme] if isinstance(readme, str) else readme\n\n        errors = []\n        for name in readmes:\n            if not (poetry_file.parent / name).exists():\n                errors.append(f\"Declared README file does not exist: {name}\")\n        return errors\n\n    def _validate_dependencies_source(self, config: dict[str, Any]) -> list[str]:\n        \"\"\"Check dependencies's source are valid\"\"\"\n        sources = {k[\"name\"] for k in config.get(\"source\", [])}\n\n        dependency_declarations: list[\n            dict[str, str | dict[str, str] | list[dict[str, str]]]\n        ] = []\n        # scan dependencies and group dependencies settings in pyproject.toml\n        if \"dependencies\" in config:\n            dependency_declarations.append(config[\"dependencies\"])\n\n        for group in config.get(\"group\", {}).values():\n            if \"dependencies\" in group:\n                dependency_declarations.append(group[\"dependencies\"])\n\n        all_referenced_sources: set[str] = set()\n\n        for dependency_declaration in dependency_declarations:\n            for declaration in dependency_declaration.values():\n                if isinstance(declaration, list):\n                    for item in declaration:\n                        if \"source\" in item:\n                            all_referenced_sources.add(item[\"source\"])\n                elif isinstance(declaration, dict) and \"source\" in declaration:\n                    all_referenced_sources.add(declaration[\"source\"])\n\n        return [\n            f'Invalid source \"{source}\" referenced in dependencies.'\n            for source in sorted(all_referenced_sources - sources)\n        ]\n\n    def handle(self) -> int:\n        from poetry.factory import Factory\n        from poetry.pyproject.toml import PyProjectTOML\n\n        # Load poetry config and display errors, if any\n        poetry_file = self.poetry.file.path\n        config = PyProjectTOML(poetry_file).poetry_config\n        check_result = Factory.validate(config, strict=True)\n\n        # Validate trove classifiers\n        project_classifiers = set(config.get(\"classifiers\", []))\n        errors, warnings = self._validate_classifiers(project_classifiers)\n        check_result[\"errors\"].extend(errors)\n        check_result[\"warnings\"].extend(warnings)\n\n        # Validate readme (files must exist)\n        if \"readme\" in config:\n            errors = self._validate_readme(config[\"readme\"], poetry_file)\n            check_result[\"errors\"].extend(errors)\n\n        check_result[\"errors\"] += self._validate_dependencies_source(config)\n\n        # Verify that lock file is consistent\n        if self.option(\"lock\") and not self.poetry.locker.is_locked():\n            check_result[\"errors\"] += [\"poetry.lock was not found.\"]\n        if self.poetry.locker.is_locked() and not self.poetry.locker.is_fresh():\n            check_result[\"errors\"] += [\n                \"poetry.lock is not consistent with pyproject.toml. Run `poetry\"\n                \" lock [--no-update]` to fix it.\"\n            ]\n\n        if not check_result[\"errors\"] and not check_result[\"warnings\"]:\n            self.info(\"All set!\")\n\n            return 0\n\n        for error in check_result[\"errors\"]:\n            self.line_error(f\"<error>Error: {error}</error>\")\n\n        for error in check_result[\"warnings\"]:\n            self.line_error(f\"<warning>Warning: {error}</warning>\")\n\n        return 1\n", "path": "src/poetry/console/commands/check.py"}]}