-
Notifications
You must be signed in to change notification settings - Fork 27
feat: --requirements-file option #748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
07afaa4
f439cb4
aa947e5
dce2fa3
4e2e5eb
9b3577b
a5e85fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -114,7 +114,7 @@ def from_dict( | |
| def create_python_environment( | ||
| cls, | ||
| directory: str, | ||
| force_generate: bool = False, | ||
| requirements_file: typing.Optional[str] = "requirements.txt", | ||
| python: typing.Optional[str] = None, | ||
| override_python_version: typing.Optional[str] = None, | ||
| app_file: typing.Optional[str] = None, | ||
|
|
@@ -125,8 +125,8 @@ def create_python_environment( | |
| If no Python executable is provided, the current system Python executable is used. | ||
|
|
||
| :param directory: the project directory to inspect. | ||
| :param force_generate: force generating "requirements.txt" to snapshot the environment | ||
| packages even if it already exists. | ||
| :param requirements_file: requirements file name relative to the project directory. If None, | ||
| capture the environment via pip freeze. | ||
| :param python: the Python executable of the environment to use for inspection. | ||
| :param override_python_version: the Python version required by the project. | ||
| :param app_file: the main application file to use for inspection. | ||
|
|
@@ -138,9 +138,8 @@ def create_python_environment( | |
| else: | ||
| module_file = app_file | ||
|
|
||
| # click.secho(' Deploying %s to server "%s"' % (directory, connect_server.url)) | ||
| _warn_on_ignored_manifest(directory) | ||
| _warn_if_no_requirements_file(directory) | ||
| _warn_if_no_requirements_file(directory, requirements_file) | ||
| _warn_if_environment_directory(directory) | ||
|
|
||
| python_version_requirement = pyproject.detect_python_version_requirement(directory) | ||
|
|
@@ -163,7 +162,7 @@ def create_python_environment( | |
| python_version_requirement = f"=={override_python_version}" | ||
|
|
||
| # with cli_feedback("Inspecting Python environment"): | ||
| environment = cls._get_python_env_info(module_file, python, force_generate) | ||
| environment = cls._get_python_env_info(module_file, python, requirements_file=requirements_file) | ||
| environment.python_version_requirement = python_version_requirement | ||
|
|
||
| if override_python_version: | ||
|
|
@@ -181,29 +180,31 @@ def create_python_environment( | |
| # Derive allow_uv from selection | ||
| environment.package_manager_allow_uv = selected_package_manager is PackageInstaller.UV | ||
|
|
||
| if force_generate: | ||
| if requirements_file is None: | ||
| _warn_on_ignored_requirements(directory, environment.filename) | ||
|
|
||
| return environment | ||
|
|
||
| @classmethod | ||
| def _get_python_env_info( | ||
| cls, file_name: str, python: typing.Optional[str], force_generate: bool = False | ||
| cls, | ||
| file_name: str, | ||
| python: typing.Optional[str], | ||
| requirements_file: typing.Optional[str] = "requirements.txt", | ||
| ) -> "Environment": | ||
| """ | ||
| Gathers the python and environment information relating to the specified file | ||
| with an eye to deploy it. | ||
|
|
||
| :param file_name: the primary file being deployed. | ||
| :param python: the optional name of a Python executable. | ||
| :param force_generate: force generating "requirements.txt" or "environment.yml", | ||
| even if it already exists. | ||
| :param requirements_file: which requirements file to read. If None, generate via pip freeze. | ||
| :return: information about the version of Python in use plus some environmental | ||
| stuff. | ||
| """ | ||
| python = which_python(python) | ||
| logger.debug("Python: %s" % python) | ||
| environment = cls._inspect_environment(python, os.path.dirname(file_name), force_generate=force_generate) | ||
| environment = cls._inspect_environment(python, os.path.dirname(file_name), requirements_file=requirements_file) | ||
| if environment.error: | ||
| raise RSConnectException(environment.error) | ||
| logger.debug("Python: %s" % python) | ||
|
|
@@ -215,21 +216,16 @@ def _inspect_environment( | |
| cls, | ||
| python: str, | ||
| directory: str, | ||
| force_generate: bool = False, | ||
| requirements_file: typing.Optional[str] = "requirements.txt", | ||
| check_output: typing.Callable[..., bytes] = subprocess.check_output, | ||
| ) -> "Environment": | ||
| """Run the environment inspector using the specified python binary. | ||
|
|
||
| Returns a dictionary of information about the environment, | ||
| or containing an "error" field if an error occurred. | ||
| """ | ||
| flags: typing.List[str] = [] | ||
| if force_generate: | ||
| flags.append("f") | ||
|
|
||
| args = [python, "-m", "rsconnect.subprocesses.inspect_environment"] | ||
| if flags: | ||
| args.append("-" + "".join(flags)) | ||
| args.extend(["--requirements-file", requirements_file or "none"]) | ||
| args.append(directory) | ||
|
|
||
| try: | ||
|
|
@@ -321,17 +317,28 @@ def _warn_on_ignored_manifest(directory: str) -> None: | |
| ) | ||
|
|
||
|
|
||
| def _warn_if_no_requirements_file(directory: str) -> None: | ||
| def _warn_if_no_requirements_file(directory: str, requirements_file: typing.Optional[str]) -> None: | ||
| """ | ||
| Checks for the existence of a file called requirements.txt in the given directory. | ||
| If it's not there, a warning will be printed. | ||
| Check that a requirements file exists, and that it lives inside the deployment directory. | ||
|
|
||
| :param directory: the directory to check in. | ||
| :param requirements_file: the name of the requirements file, or None to skip the check. | ||
| """ | ||
| if not os.path.exists(os.path.join(directory, "requirements.txt")): | ||
| if requirements_file is None: | ||
| return | ||
|
|
||
| directory_path = pathlib.Path(directory) | ||
| requirements_file_path = directory_path / pathlib.Path(requirements_file) | ||
| if directory_path not in requirements_file_path.parents: | ||
| click.secho( | ||
| " Warning: The requirements file '%s' is outside of the deployment directory.\n" % requirements_file, | ||
| fg="red", | ||
| ) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that the user can now provide a requirements file, the used requirements file might be "../../../something.txt", we want to make sure the user has clear that's not meant to happen. |
||
|
|
||
| if not requirements_file_path.exists(): | ||
| click.secho( | ||
| " Warning: Capturing the environment using 'pip freeze'.\n" | ||
| " Consider creating a requirements.txt file instead.", | ||
| " Consider creating a %s file instead." % requirements_file, | ||
|
Comment on lines
340
to
+341
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My initial impulse was that if a user explicitly passes in a requirements file path and we can't find a file there, we should emit an error, not a warning. But I realize we don't have any way to know whether the path we have is one that was explicitly provided or the default from the user, so I guess I'm just noting my weird feeling here. |
||
| fg="yellow", | ||
| ) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used by
vetiver-pythonpackage, which writes an application from scratch and deploys it using rsconnect. We can take for granted the requirements file is always "requirements.txt" here as it's driven byvetiver-python