Skip to content

Commit ffe8a64

Browse files
committed
Move the interpreter into the Environment info
1 parent d135e3f commit ffe8a64

File tree

3 files changed

+57
-22
lines changed

3 files changed

+57
-22
lines changed

rsconnect/actions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ def deploy_app(
372372
)
373373
)
374374

375-
_, environment = Environment.create_python_environment(
375+
environment = Environment.create_python_environment(
376376
directory, # pyright: ignore
377377
force_generate,
378378
python,

rsconnect/environment.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
"""Detects the configuration of a Python environment.
2+
3+
Given a directory and a Python executable, this module inspects the environment
4+
and returns information about the Python version and the environment itself.
5+
6+
To inspect the environment it relies on a subprocess that runs the `rsconnect.subprocesses.environment`
7+
module. This module is responsible for gathering the environment information and returning it in a JSON format.
8+
"""
9+
110
import typing
211
import sys
312
import dataclasses
@@ -23,11 +32,17 @@ class Environment:
2332

2433
DATA_FIELDS = {f.name for f in dataclasses.fields(EnvironmentData)}
2534

26-
def __init__(self, data: EnvironmentData, python_version_requirement: typing.Optional[str] = None):
35+
def __init__(
36+
self,
37+
data: EnvironmentData,
38+
python_interpreter: typing.Optional[str] = None,
39+
python_version_requirement: typing.Optional[str] = None,
40+
):
2741
self._data = data
2842

2943
# Fields that are not loaded from the environment subprocess
3044
self.python_version_requirement = python_version_requirement
45+
self.python_interpreter = python_interpreter
3146

3247
def __getattr__(self, name: str) -> typing.Any:
3348
# We directly proxy the attributes of the EnvironmentData object
@@ -42,9 +57,18 @@ def __setattr__(self, name: str, value: typing.Any) -> None:
4257
super().__setattr__(name, value)
4358

4459
@classmethod
45-
def from_dict(cls, data: dict[str, typing.Any]) -> "Environment":
46-
"""Create an Environment instance from the JSON representation of EnvironmentData."""
47-
return cls(_MakeEnvironmentData(**data))
60+
def from_dict(
61+
cls,
62+
data: dict[str, typing.Any],
63+
python_interpreter: typing.Optional[str] = None,
64+
python_version_requirement: typing.Optional[str] = None,
65+
) -> "Environment":
66+
"""Create an Environment instance from the dictionary representation of EnvironmentData."""
67+
return cls(
68+
_MakeEnvironmentData(**data),
69+
python_interpreter=python_interpreter,
70+
python_version_requirement=python_version_requirement,
71+
)
4872

4973
@classmethod
5074
def create_python_environment(
@@ -54,7 +78,20 @@ def create_python_environment(
5478
python: typing.Optional[str] = None,
5579
override_python_version: typing.Optional[str] = None,
5680
app_file: typing.Optional[str] = None,
57-
) -> typing.Tuple[str, "Environment"]:
81+
) -> "Environment":
82+
"""Given a project directory and a Python executable, return Environment information.
83+
84+
If no Python executable is provided, the current system Python executable is used.
85+
86+
:param directory: the project directory to inspect.
87+
:param force_generate: force generating "requirements.txt" to snapshot the environment
88+
packages even if it already exists.
89+
:param python: the Python executable of the environment to use for inspection.
90+
:param override_python_version: the Python version required by the project.
91+
:param app_file: the main application file to use for inspection.
92+
93+
:return: a tuple containing the Python executable of the environment and the Environment object.
94+
"""
5895
if app_file is None:
5996
module_file = fake_module_file_from_directory(directory)
6097
else:
@@ -75,7 +112,7 @@ def create_python_environment(
75112
python_version_requirement = f"=={override_python_version}"
76113

77114
# with cli_feedback("Inspecting Python environment"):
78-
python_interpreter, environment = cls._get_python_env_info(module_file, python, force_generate)
115+
environment = cls._get_python_env_info(module_file, python, force_generate)
79116
environment.python_version_requirement = python_version_requirement
80117

81118
if override_python_version:
@@ -86,12 +123,10 @@ def create_python_environment(
86123
if force_generate:
87124
_warn_on_ignored_requirements(directory, environment.filename)
88125

89-
return python_interpreter, environment
126+
return environment
90127

91128
@classmethod
92-
def _get_python_env_info(
93-
cls, file_name: str, python: str | None, force_generate: bool = False
94-
) -> tuple[str, "Environment"]:
129+
def _get_python_env_info(cls, file_name: str, python: str | None, force_generate: bool = False) -> "Environment":
95130
"""
96131
Gathers the python and environment information relating to the specified file
97132
with an eye to deploy it.
@@ -110,7 +145,7 @@ def _get_python_env_info(
110145
raise RSConnectException(environment.error)
111146
logger.debug("Python: %s" % python)
112147
logger.debug("Environment: %s" % pprint.pformat(environment._asdict()))
113-
return python, environment
148+
return environment
114149

115150
@classmethod
116151
def _inspect_environment(
@@ -150,7 +185,7 @@ def _inspect_environment(
150185
raise RSConnectException(f"Error creating environment: {system_error_message}")
151186

152187
try:
153-
return cls.from_dict(environment_data)
188+
return cls.from_dict(environment_data, python_interpreter=python)
154189
except TypeError as e:
155190
raise RSConnectException("Error constructing environment object") from e
156191

rsconnect/main.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ def deploy_notebook(
913913
app_mode = AppModes.JUPYTER_NOTEBOOK if not static else AppModes.STATIC
914914

915915
base_dir = dirname(file)
916-
python, environment = Environment.create_python_environment(
916+
environment = Environment.create_python_environment(
917917
base_dir,
918918
app_file=file,
919919
force_generate=force_generate,
@@ -944,7 +944,7 @@ def deploy_notebook(
944944
ce.make_bundle(
945945
make_notebook_html_bundle,
946946
file,
947-
python,
947+
environment.python,
948948
hide_all_input,
949949
hide_tagged_input,
950950
)
@@ -1054,7 +1054,7 @@ def deploy_voila(
10541054
set_verbosity(verbose)
10551055
output_params(ctx, locals().items())
10561056
app_mode = AppModes.JUPYTER_VOILA
1057-
_, environment = Environment.create_python_environment(
1057+
environment = Environment.create_python_environment(
10581058
path if isdir(path) else dirname(path), force_generate, python, override_python_version
10591059
)
10601060

@@ -1271,7 +1271,7 @@ def deploy_quarto(
12711271
environment = None
12721272
if "jupyter" in engines:
12731273
with cli_feedback("Inspecting Python environment"):
1274-
_, environment = Environment.create_python_environment(
1274+
environment = Environment.create_python_environment(
12751275
base_dir, force_generate=force_generate, override_python_version=override_python_version
12761276
)
12771277

@@ -1614,7 +1614,7 @@ def deploy_app(
16141614
set_verbosity(verbose)
16151615
entrypoint = validate_entry_point(entrypoint, directory)
16161616
extra_files_list = validate_extra_files(directory, extra_files)
1617-
_, environment = Environment.create_python_environment(
1617+
environment = Environment.create_python_environment(
16181618
directory, force_generate, python, override_python_version=override_python_version
16191619
)
16201620

@@ -1785,7 +1785,7 @@ def write_manifest_notebook(
17851785
raise RSConnectException("manifest.json already exists. Use --overwrite to overwrite.")
17861786

17871787
with cli_feedback("Inspecting Python environment"):
1788-
_, environment = Environment.create_python_environment(
1788+
environment = Environment.create_python_environment(
17891789
base_dir,
17901790
force_generate=force_generate,
17911791
python=python,
@@ -1897,7 +1897,7 @@ def write_manifest_voila(
18971897
raise RSConnectException("manifest.json already exists. Use --overwrite to overwrite.")
18981898

18991899
with cli_feedback("Inspecting Python environment"):
1900-
_, environment = Environment.create_python_environment(
1900+
environment = Environment.create_python_environment(
19011901
base_dir,
19021902
force_generate=force_generate,
19031903
override_python_version=override_python_version,
@@ -2026,7 +2026,7 @@ def write_manifest_quarto(
20262026
environment = None
20272027
if "jupyter" in engines:
20282028
with cli_feedback("Inspecting Python environment"):
2029-
_, environment = Environment.create_python_environment(
2029+
environment = Environment.create_python_environment(
20302030
base_dir, force_generate=force_generate, override_python_version=override_python_version, python=python
20312031
)
20322032

@@ -2271,7 +2271,7 @@ def _write_framework_manifest(
22712271
raise RSConnectException("manifest.json already exists. Use --overwrite to overwrite.")
22722272

22732273
with cli_feedback("Inspecting Python environment"):
2274-
_, environment = Environment.create_python_environment(
2274+
environment = Environment.create_python_environment(
22752275
directory,
22762276
force_generate=force_generate,
22772277
override_python_version=override_python_version,

0 commit comments

Comments
 (0)