Skip to content

Commit 5ca8a10

Browse files
committed
lsp: Use fallback env when possible
If a client provides the `esbonio.sphinx.fallbackEnv`, repurpose the server's Python interpreter to launch the Sphinx agent.
1 parent a5dc6de commit 5ca8a10

File tree

1 file changed

+54
-19
lines changed
  • lib/esbonio/esbonio/server/features/sphinx_manager

1 file changed

+54
-19
lines changed

lib/esbonio/esbonio/server/features/sphinx_manager/config.py

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import importlib.util
44
import logging
55
import pathlib
6+
import sys
67
from typing import Any
78
from typing import Optional
89

@@ -60,6 +61,12 @@ class SphinxConfig:
6061
cwd: str = attrs.field(default="${scopeFsPath}")
6162
"""The working directory to use."""
6263

64+
fallback_env: str | None = attrs.field(default=None)
65+
"""Location of the fallback environment to use.
66+
67+
Intended to be used by clients to handle the case where the user has not configured
68+
``python_command`` themselves."""
69+
6370
python_path: list[pathlib.Path] = attrs.field(factory=list)
6471
"""The value of ``PYTHONPATH`` to use when injecting the sphinx agent into the
6572
target environment"""
@@ -89,8 +96,8 @@ def resolve(
8996
The fully resolved config object to use.
9097
If ``None``, a valid configuration could not be created.
9198
"""
92-
python_path = self._resolve_python_path(logger)
93-
if len(python_path) == 0:
99+
python_command, python_path = self._resolve_python(logger)
100+
if len(python_path) == 0 or len(python_command) == 0:
94101
return None
95102

96103
cwd = self._resolve_cwd(uri, workspace, logger)
@@ -109,7 +116,7 @@ def resolve(
109116
config_overrides=self.config_overrides,
110117
cwd=cwd,
111118
env_passthrough=self.env_passthrough,
112-
python_command=self.python_command,
119+
python_command=python_command,
113120
build_command=build_command,
114121
python_path=python_path,
115122
)
@@ -158,12 +165,25 @@ def _resolve_cwd(
158165

159166
return None
160167

161-
def _resolve_python_path(self, logger: logging.Logger) -> list[pathlib.Path]:
162-
"""Return the list of paths to put on the sphinx agent's ``PYTHONPATH``
168+
def _resolve_python(
169+
self, logger: logging.Logger
170+
) -> tuple[list[str], list[pathlib.Path]]:
171+
"""Return the python configuration to use when launching the sphinx agent.
172+
173+
The first element of the returned tuple is the command to use when running the
174+
sphinx agent. This could be as simple as the path to the python interpreter in a
175+
particular virtual environment or a complex command such as
176+
``hatch -e docs run python``.
163177
164178
Using the ``PYTHONPATH`` environment variable, we can inject additional Python
165-
packages into the user's Python environment. This method will locate the
166-
installation path of the sphinx agent and return it.
179+
packages into the user's Python environment. This method also locates the
180+
installation path of the sphinx agent and returns it in the second element of the
181+
tuple.
182+
183+
Finally, if the user has not configured a python environment and the client has
184+
set the ``fallback_env`` option, this method will construct a command based on
185+
the current interpreter to create an isolated environment based on
186+
``fallback_env``.
167187
168188
Parameters
169189
----------
@@ -172,19 +192,34 @@ def _resolve_python_path(self, logger: logging.Logger) -> list[pathlib.Path]:
172192
173193
Returns
174194
-------
175-
List[pathlib.Path]
176-
The list of paths to Python packages to inject into the sphinx agent's target
177-
environment. If empty, the ``esbonio.sphinx_agent`` package was not found.
195+
tuple[list[str], list[pathlib.Path]]
196+
A tuple of the form ``(python_command, python_path)``.
178197
"""
179-
if len(self.python_path) > 0:
180-
return self.python_path
181-
182-
if (sphinx_agent := get_module_path("esbonio.sphinx_agent")) is None:
183-
logger.error("Unable to locate the sphinx agent")
184-
return []
185-
186-
python_path = [sphinx_agent]
187-
return python_path
198+
if len(python_path := list(self.python_path)) == 0:
199+
if (sphinx_agent := get_module_path("esbonio.sphinx_agent")) is None:
200+
logger.error("Unable to locate the sphinx agent")
201+
return [], []
202+
203+
python_path.append(sphinx_agent)
204+
205+
if len(python_command := list(self.python_command)) == 0:
206+
if self.fallback_env is None:
207+
logger.error("No python command configured")
208+
return [], []
209+
210+
if not (fallback_env := pathlib.Path(self.fallback_env)).exists():
211+
logger.error(
212+
"Provided fallback environment %s does not exist", fallback_env
213+
)
214+
return [], []
215+
216+
# Since the client has provided a fallback environment we can isolate the
217+
# current Python interpreter from its environment and reuse it.
218+
logger.debug("Using fallback environment")
219+
python_path.append(fallback_env)
220+
python_command.extend([sys.executable, "-S"])
221+
222+
return python_command, python_path
188223

189224
def _resolve_build_command(self, uri: Uri, logger: logging.Logger) -> list[str]:
190225
"""Return the ``sphinx-build`` command to use.

0 commit comments

Comments
 (0)