Skip to content
This repository was archived by the owner on Jun 30, 2024. It is now read-only.

Commit c703d15

Browse files
committed
Fix: Better document how poetry-fix works; error if it doesn't process
BookServer and the RunestoneComponents.
1 parent e24266f commit c703d15

File tree

2 files changed

+92
-12
lines changed

2 files changed

+92
-12
lines changed

docker/docker_tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
#
4646
# Imports and bootstrap
4747
# =====================
48-
# These are listed in the order prescribed by PEP 8, with exceptions noted below.
48+
# These are listed in the order prescribed by `PEP 8 <http://www.python.org/dev/peps/pep-0008/#imports>`_, with exceptions noted below.
4949
#
5050
# There's a fair amount of bootstrap code here to download and install required imports and their dependencies.
5151
#

runestone_poetry_project/poetry_fix.py

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,68 @@
11
# ***********************************
22
# |docname| - Work around Poetry bugs
33
# ***********************************
4-
# This script contains two fixes for Poetry bugs: one bug which manifests when the ``--no-dev`` flag is passed to ``poetry install/update`` and another which occurs when the ``--no-dev`` flag isn't passed. It doesn't provide a fix to a third bug, discussed below
4+
# This script contains workarounds for Poetry design decisions and bugs:
5+
#
6+
# #. Poetry doesn't support either/or dependencies, but this project needs them. Specifically, we want to install either the released, PyPI-published version of the RunestoneComponents and the BookServer, or the development version of these projects which are cloned to the local filesystem. The ``pyproject.toml`` file therefore contains (with all other dependencies removed for clarity):
7+
#
8+
# .. code-block:: text
9+
#
10+
# [tool.poetry.dependencies]
11+
# bookserver = "^1.0.0"
12+
# runestone = "^6.1.0"
13+
#
14+
# [tool.poetry.dev-dependencies]
15+
# bookserver = { path = "../BookServer", develop = true }
16+
# runestone = { path = "../RunestoneComponents", develop = true }
17+
#
18+
# This breaks Poetry, since it looks for BOTH dependencies during dependency resolution. To work around this, `rename_pyproject <rename_pyproject>` changes this to:
19+
#
20+
# .. code-block:: text
21+
#
22+
# [tool.poetry.dependencies]
23+
# bookserver = "^1.0.0"
24+
# runestone = "^6.1.0"
25+
#
26+
# [tool.no-poetry.dev-dependencies] # <== CHANGED!
27+
# bookserver = { path = "../BookServer", develop = true }
28+
# runestone = { path = "../RunestoneComponents", develop = true }
29+
#
30+
# ...in production mode; it does the opposite (changes ``[tool.poetry.dependencies]`` to ``[tool.no-poetry.dependencies]``) in development mode. This hides the modified section from Poetry, so the file now looks like an either/or project.
31+
#
32+
# #. Poetry doesn't install development dependencies in projects included through a path dependency. As a workaround, this script creates additional "projects" which only contain the development dependencies, but places these in the production dependencies section of the "project". For example, the BookServer ``pyproject.toml`` contains:
33+
#
34+
# .. code-block:: text
35+
#
36+
# [tool.poetry.dev-dependencies]
37+
# black = "~= 22.0"
38+
# console-ctrl = "^0.1.0"
39+
# ...many more, which are omitted for clarity...
40+
#
41+
# Poetry won't install these. Therefore, `make_dev_pyproject <make_dev_pyproject>` creates a "project" named ``bookserver-dev`` which contains:
42+
#
43+
# .. code-block:: text
44+
#
45+
# [tool.poetry.dependencies] # <== CHANGED!
46+
# black = "~= 22.0"
47+
# console-ctrl = "^0.1.0"
48+
# ...many more, which are omitted for clarity...
49+
#
50+
# This also means that the ``pyproject.toml`` file must be manually edited to include a reference to this "project":
51+
#
52+
# .. code-block:: text
53+
#
54+
# [tool.poetry.dev-dependencies]
55+
# bookserver = { path = "../BookServer", develop = true }
56+
# bookserver-dev = { path = "../bookserver-dev", develop = true } # <== MANUALLY ADDED!
57+
#
58+
# #. Poetry generates invalid package metadata for local path dependencies, so that running ``pip show click`` results in a bunch of exceptions. This program doesn't provide a fix for this bug.
59+
#
60+
# ...and that's how using Poetry makes dependency management easier...
61+
#
562
#
663
# `Invalid package METADATA <https://github.com/python-poetry/poetry/issues/3148>`_
764
# =====================================================================================
8-
# Per this issue, Poetry generates invalid package metadata for local path dependencies. For example, the last few lines of ``.venv/lib/python3.8/site-packages/runestone_poetry_project-0.1.0.dist-info/METADATA`` contain:
65+
# Per the issue linked in the title above, Poetry generates invalid package metadata for local path dependencies (tested on Poetry v1.1.14). For example, the last few lines of ``.venv/lib/python3.8/site-packages/runestone_poetry_project-0.1.0.dist-info/METADATA`` contain:
966
#
1067
# .. code-block:: text
1168
#
@@ -45,7 +102,7 @@
45102
# Requires-Dist: sphinxcontrib-paverutils (>=1.17)
46103
# Requires-Dist: stripe (>=2.0.0,<3.0.0)
47104
#
48-
# ... along with a similar fix to the ``METADATA`` for ``bookserver_dev`` allow ``pip`` to run successfully.
105+
# ... along with a similar fix to the ``METADATA`` for ``bookserver_dev`` allows ``pip`` to run successfully.
49106
#
50107
#
51108
# TODO
@@ -55,11 +112,12 @@
55112
#
56113
# Imports
57114
# =======
58-
# These are listed in the order prescribed by `PEP 8`_.
115+
# These are listed in the order prescribed by `PEP 8 <http://www.python.org/dev/peps/pep-0008/#imports>`_.
59116
#
60117
# Standard library
61118
# ----------------
62119
from pathlib import Path
120+
import sys
63121
from typing import Any, Dict, Set
64122

65123
# Third-party imports
@@ -72,7 +130,6 @@
72130
# -------------------------
73131
# None.
74132
#
75-
#
76133
# Fix for ``dev-dependencies`` in subprojects
77134
# ===========================================
78135
# Given a main Poetry ``pyproject.toml``, these functions look for all subprojects included via path dependencies, creating additional subprojects named ``projectname-dev`` in which the subproject's dev-dependencies become dependencies in the newly-created subproject. This is a workaround for Poetry's inability to install the dev dependencies for a sub project included via a path requirement. To use this, in the main project, do something like:
@@ -124,12 +181,14 @@ def walk_dependencies(
124181
project_path: Path,
125182
# See `walked_paths_set`.
126183
walked_paths_set: Set[Path],
184+
# See `poetry_paths_set`.
185+
poetry_paths_set: Set[Path],
127186
):
128187
key = "dependencies" if is_deps else "dev-dependencies"
129188
for dep in poetry_dict.get(key, {}).values():
130189
pth = dep.get("path", "") if isinstance(dep, dict) else None
131190
if pth:
132-
walk_pyproject(project_path / pth, walked_paths_set)
191+
walk_pyproject(project_path / pth, walked_paths_set, poetry_paths_set)
133192

134193

135194
# Given a ``pyproject.toml``, optionally create a dev dependencies project and walk all requirements with path dependencies.
@@ -138,6 +197,8 @@ def walk_pyproject(
138197
project_path: Path,
139198
# _`walked_paths_set`: a set of Paths already walked.
140199
walked_paths_set: Set[Path],
200+
# _`poetry_paths_set`: a set of Paths that contained a Poetry project. This is a strict subset of walked_paths_set_.
201+
poetry_paths_set: Set[Path],
141202
# True if this is the root ``pyproject.toml`` file -- no dev dependencies will be created for it.
142203
is_root: bool = False,
143204
):
@@ -153,22 +214,41 @@ def walk_pyproject(
153214
d = toml.load(project_path / "pyproject.toml")
154215
except FileNotFoundError:
155216
return
217+
poetry_paths_set.add(project_path)
156218
tp = d["tool"]["poetry"]
157-
walk_dependencies(tp, True, project_path, walked_paths_set)
158-
walk_dependencies(tp, False, project_path, walked_paths_set)
219+
# Search both the dependencies and dev dependencies in this project for path dependencies.
220+
walk_dependencies(tp, True, project_path, walked_paths_set, poetry_paths_set)
221+
walk_dependencies(tp, False, project_path, walked_paths_set, poetry_paths_set)
159222

160223
# (Usually) process this file.
161224
if not is_root:
162225
create_dev_dependencies(project_path)
163226

164227

228+
# .. _make_dev_pyproject:
229+
#
165230
# Core function: run the whole process on the ``pyproject.toml`` in the current directory.
166231
def make_dev_pyproject():
167-
walk_pyproject(Path("."), set(), True)
232+
project_paths_set = set()
233+
walk_pyproject(Path("."), set(), project_paths_set, True)
234+
235+
# Check that we processed the BookServer and the RunestoneComponents.
236+
found_bookserver = False
237+
found_runestone_components = False
238+
for path in project_paths_set:
239+
name = path.name
240+
found_bookserver |= name == "BookServer"
241+
found_runestone_components |= name == "RunestoneComponents"
242+
if not found_bookserver:
243+
sys.exit("Error: did not process the BookServer Poetry project.")
244+
if not found_runestone_components:
245+
sys.exit("Error: did not process the RunestoneComponents Poetry project.")
168246

169247

170-
# Fix for the main ``pyproject.toml``
171-
# ===================================
248+
# .. _rename_pyproject:
249+
#
250+
# Workaround for the main ``pyproject.toml``
251+
# ==========================================
172252
# This function updates the ``pyproject.toml`` in the current directory by switching between a section named ``[tool.poetry.dev-dependencies]`` when in development mode or ``[tool.no-poetry.dev-dependencies]`` when not in development mode. This is because Poetry does not support either/or dependencies: either resolve dependency x in dev mode, or dependency y when not in dev mode. Instead, it takes a both/and approach: during its dependency resolution phase, it resolves ALL dependencies, then installs a subset (such all non-dev dependencies, or dev and non-dev dependencies). Quoting from the `manual <https://python-poetry.org/docs/master/managing-dependencies/>`_:
173253
#
174254
# All dependencies must be compatible with each other across groups since they will be resolved regardless of whether they are required for installation or not (see Installing group dependencies).

0 commit comments

Comments
 (0)