@@ -114,7 +114,7 @@ def from_dict(
114114 def create_python_environment (
115115 cls ,
116116 directory : str ,
117- force_generate : bool = False ,
117+ requirements_file : typing . Optional [ str ] = "requirements.txt" ,
118118 python : typing .Optional [str ] = None ,
119119 override_python_version : typing .Optional [str ] = None ,
120120 app_file : typing .Optional [str ] = None ,
@@ -125,8 +125,8 @@ def create_python_environment(
125125 If no Python executable is provided, the current system Python executable is used.
126126
127127 :param directory: the project directory to inspect.
128- :param force_generate: force generating "requirements.txt" to snapshot the environment
129- packages even if it already exists .
128+ :param requirements_file: requirements file name relative to the project directory. If None,
129+ capture the environment via pip freeze .
130130 :param python: the Python executable of the environment to use for inspection.
131131 :param override_python_version: the Python version required by the project.
132132 :param app_file: the main application file to use for inspection.
@@ -138,9 +138,8 @@ def create_python_environment(
138138 else :
139139 module_file = app_file
140140
141- # click.secho(' Deploying %s to server "%s"' % (directory, connect_server.url))
142141 _warn_on_ignored_manifest (directory )
143- _warn_if_no_requirements_file (directory )
142+ _warn_if_no_requirements_file (directory , requirements_file )
144143 _warn_if_environment_directory (directory )
145144
146145 python_version_requirement = pyproject .detect_python_version_requirement (directory )
@@ -163,7 +162,7 @@ def create_python_environment(
163162 python_version_requirement = f"=={ override_python_version } "
164163
165164 # with cli_feedback("Inspecting Python environment"):
166- environment = cls ._get_python_env_info (module_file , python , force_generate )
165+ environment = cls ._get_python_env_info (module_file , python , requirements_file = requirements_file )
167166 environment .python_version_requirement = python_version_requirement
168167
169168 if override_python_version :
@@ -181,29 +180,31 @@ def create_python_environment(
181180 # Derive allow_uv from selection
182181 environment .package_manager_allow_uv = selected_package_manager is PackageInstaller .UV
183182
184- if force_generate :
183+ if requirements_file is None :
185184 _warn_on_ignored_requirements (directory , environment .filename )
186185
187186 return environment
188187
189188 @classmethod
190189 def _get_python_env_info (
191- cls , file_name : str , python : typing .Optional [str ], force_generate : bool = False
190+ cls ,
191+ file_name : str ,
192+ python : typing .Optional [str ],
193+ requirements_file : typing .Optional [str ] = "requirements.txt" ,
192194 ) -> "Environment" :
193195 """
194196 Gathers the python and environment information relating to the specified file
195197 with an eye to deploy it.
196198
197199 :param file_name: the primary file being deployed.
198200 :param python: the optional name of a Python executable.
199- :param force_generate: force generating "requirements.txt" or "environment.yml",
200- even if it already exists.
201+ :param requirements_file: which requirements file to read. If None, generate via pip freeze.
201202 :return: information about the version of Python in use plus some environmental
202203 stuff.
203204 """
204205 python = which_python (python )
205206 logger .debug ("Python: %s" % python )
206- environment = cls ._inspect_environment (python , os .path .dirname (file_name ), force_generate = force_generate )
207+ environment = cls ._inspect_environment (python , os .path .dirname (file_name ), requirements_file = requirements_file )
207208 if environment .error :
208209 raise RSConnectException (environment .error )
209210 logger .debug ("Python: %s" % python )
@@ -215,21 +216,16 @@ def _inspect_environment(
215216 cls ,
216217 python : str ,
217218 directory : str ,
218- force_generate : bool = False ,
219+ requirements_file : typing . Optional [ str ] = "requirements.txt" ,
219220 check_output : typing .Callable [..., bytes ] = subprocess .check_output ,
220221 ) -> "Environment" :
221222 """Run the environment inspector using the specified python binary.
222223
223224 Returns a dictionary of information about the environment,
224225 or containing an "error" field if an error occurred.
225226 """
226- flags : typing .List [str ] = []
227- if force_generate :
228- flags .append ("f" )
229-
230227 args = [python , "-m" , "rsconnect.subprocesses.inspect_environment" ]
231- if flags :
232- args .append ("-" + "" .join (flags ))
228+ args .extend (["--requirements-file" , requirements_file or "none" ])
233229 args .append (directory )
234230
235231 try :
@@ -321,17 +317,28 @@ def _warn_on_ignored_manifest(directory: str) -> None:
321317 )
322318
323319
324- def _warn_if_no_requirements_file (directory : str ) -> None :
320+ def _warn_if_no_requirements_file (directory : str , requirements_file : typing . Optional [ str ] ) -> None :
325321 """
326- Checks for the existence of a file called requirements.txt in the given directory.
327- If it's not there, a warning will be printed.
322+ Check that a requirements file exists, and that it lives inside the deployment directory.
328323
329324 :param directory: the directory to check in.
325+ :param requirements_file: the name of the requirements file, or None to skip the check.
330326 """
331- if not os .path .exists (os .path .join (directory , "requirements.txt" )):
327+ if requirements_file is None :
328+ return
329+
330+ directory_path = pathlib .Path (directory )
331+ requirements_file_path = directory_path / pathlib .Path (requirements_file )
332+ if directory_path not in requirements_file_path .parents :
333+ click .secho (
334+ " Warning: The requirements file '%s' is outside of the deployment directory.\n " % requirements_file ,
335+ fg = "red" ,
336+ )
337+
338+ if not requirements_file_path .exists ():
332339 click .secho (
333340 " Warning: Capturing the environment using 'pip freeze'.\n "
334- " Consider creating a requirements.txt file instead." ,
341+ " Consider creating a %s file instead." % requirements_file ,
335342 fg = "yellow" ,
336343 )
337344
0 commit comments