3
3
import importlib .util
4
4
import logging
5
5
import pathlib
6
+ import sys
6
7
from typing import Any
7
8
from typing import Optional
8
9
@@ -60,6 +61,12 @@ class SphinxConfig:
60
61
cwd : str = attrs .field (default = "${scopeFsPath}" )
61
62
"""The working directory to use."""
62
63
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
+
63
70
python_path : list [pathlib .Path ] = attrs .field (factory = list )
64
71
"""The value of ``PYTHONPATH`` to use when injecting the sphinx agent into the
65
72
target environment"""
@@ -89,8 +96,8 @@ def resolve(
89
96
The fully resolved config object to use.
90
97
If ``None``, a valid configuration could not be created.
91
98
"""
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 :
94
101
return None
95
102
96
103
cwd = self ._resolve_cwd (uri , workspace , logger )
@@ -109,7 +116,7 @@ def resolve(
109
116
config_overrides = self .config_overrides ,
110
117
cwd = cwd ,
111
118
env_passthrough = self .env_passthrough ,
112
- python_command = self . python_command ,
119
+ python_command = python_command ,
113
120
build_command = build_command ,
114
121
python_path = python_path ,
115
122
)
@@ -158,12 +165,25 @@ def _resolve_cwd(
158
165
159
166
return None
160
167
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``.
163
177
164
178
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``.
167
187
168
188
Parameters
169
189
----------
@@ -172,19 +192,34 @@ def _resolve_python_path(self, logger: logging.Logger) -> list[pathlib.Path]:
172
192
173
193
Returns
174
194
-------
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)``.
178
197
"""
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
188
223
189
224
def _resolve_build_command (self , uri : Uri , logger : logging .Logger ) -> list [str ]:
190
225
"""Return the ``sphinx-build`` command to use.
0 commit comments