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+
110import typing
211import sys
312import 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
0 commit comments