Skip to content
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

chore: adding a warning message about Python 3.8 #1019

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ documentation.

`ggshield` works on macOS, Linux and Windows.

It requires **Python 3.8 and newer** (except for standalone packages) and git.
It requires **Python 3.8 or above** (except for standalone packages) and git.

:warning: Python 3.8 is no longer supported by the Python Software Foundation since October, 14th 2024. GGShield will soon require Python 3.9 or above to run.
sevbch marked this conversation as resolved.
Show resolved Hide resolved

Some commands require additional programs:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!--
A new scriv changelog fragment.

Uncomment the section that is right (remove the HTML comment wrapper).
-->

<!--
### Removed

- A bullet item for the Removed category.

-->
<!--
### Added

- A bullet item for the Added category.

-->

<!-- ### Changed -->

### Deprecated

- Added warning message about future removal of Python 3.8 support.

<!--
### Fixed

- A bullet item for the Fixed category.

-->
<!--
### Security

- A bullet item for the Security category.

-->
5 changes: 5 additions & 0 deletions ggshield/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ def _set_color(ctx: click.Context):
def _display_deprecation_message(cfg: Config) -> None:
for message in cfg.user_config.deprecation_messages:
ui.display_warning(message)
if sys.version_info < (3, 9):
ui.display_warning(
"Python 3.8 is no longer supported by the Python Software Foundation. "
"GGShield will soon require Python 3.9 or above to run."
)


def _check_for_updates(check_for_updates: bool) -> None:
Expand Down
41 changes: 29 additions & 12 deletions tests/unit/cmd/auth/test_login.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import sys
import urllib.parse as urlparse
from datetime import datetime, timedelta, timezone
from enum import IntEnum, auto
Expand Down Expand Up @@ -321,7 +322,7 @@ def test_existing_token_no_expiry(self, instance_url, cli_fs_runner, monkeypatch
self._webbrowser_open_mock.assert_not_called()

self._assert_last_print(
output, "ggshield is already authenticated without an expiry date"
output, "ggshield is already authenticated without an expiry date\n"
)

@pytest.mark.parametrize(
Expand Down Expand Up @@ -353,7 +354,7 @@ def test_existing_non_expired_token(
self._request_mock.assert_all_requests_happened()

self._assert_last_print(
output, f"ggshield is already authenticated until {str_date}, 2100"
output, f"ggshield is already authenticated until {str_date}, 2100\n"
)

def test_auth_login_recreates_token_if_deleted_server_side(
Expand Down Expand Up @@ -394,7 +395,7 @@ def test_no_port_available_exits_error(self, cli_fs_runner, monkeypatch):
assert exit_code == ExitCode.UNEXPECTED_ERROR

self._webbrowser_open_mock.assert_not_called()
self._assert_last_print(output, "Error: Could not find unoccupied port.")
self._assert_last_print(output, "Error: Could not find unoccupied port.\n")
self._assert_config_is_empty()

@pytest.mark.parametrize(
Expand Down Expand Up @@ -426,7 +427,7 @@ def test_invalid_oauth_params_exits_error(
self._webbrowser_open_mock.assert_called_once()
self._assert_last_print(
output,
"Error: Invalid code or state received from the callback.",
"Error: Invalid code or state received from the callback.\n",
)
self._assert_config_is_empty()

Expand All @@ -447,18 +448,18 @@ def test_invalid_code_exchange_exits_error(self, cli_fs_runner, monkeypatch):

self._request_mock.assert_all_requests_happened()
self._webbrowser_open_mock.assert_called_once()
self._assert_last_print(output, "Error: Cannot create a token: kaboom.")
self._assert_last_print(output, "Error: Cannot create a token: kaboom.\n")

@pytest.mark.parametrize(
("login_result", "message"),
(
(
LoginResult.GARBAGE_HTML_RESPONSE,
"Error: Server response is not JSON (HTTP code: 418).",
"Error: Server response is not JSON (HTTP code: 418).\n",
),
(
LoginResult.GARBAGE_NO_TOKEN_RESPONSE,
"Error: Server did not provide the created token.",
"Error: Server did not provide the created token.\n",
),
),
)
Expand Down Expand Up @@ -495,7 +496,7 @@ def test_invalid_token_exits_error(self, cli_fs_runner, monkeypatch):
self._assert_open_url()

self._request_mock.assert_all_requests_happened()
self._assert_last_print(output, "Error: The created token is invalid.")
self._assert_last_print(output, "Error: The created token is invalid.\n")

@pytest.mark.parametrize("token_name", [None, "some token name"])
@pytest.mark.parametrize("lifetime", [None, 0, 1, 365])
Expand Down Expand Up @@ -562,6 +563,12 @@ def test_valid_process(
'You do not need to run "ggshield auth login" again. Future requests will automatically use the token.\n'
)

if sys.version_info < (3, 9):
message += (
"Warning: Python 3.8 is no longer supported by the Python Software Foundation. "
"GGShield will soon require Python 3.9 or above to run.\n"
)

assert output.endswith(message)

self._assert_config("mysupertoken")
Expand Down Expand Up @@ -758,7 +765,12 @@ def _assert_last_print(output: str, expected_str: str):
"""
assert that the last log output is the same as the one passed in param
"""
assert output.rsplit("\n", 2)[-2] == expected_str
if sys.version_info < (3, 9) and "Error:" not in expected_str:
expected_str += (
"Warning: Python 3.8 is no longer supported by the Python Software Foundation. "
"GGShield will soon require Python 3.9 or above to run.\n"
)
assert output.endswith(expected_str)

def _assert_open_url(
self,
Expand Down Expand Up @@ -857,19 +869,19 @@ def _wait_for_callback(self, *args, **kwargs):
"web",
"https://dashboard.gitguardian.com",
"https://onprem.gitguardian.com/auth/sso/1e0f7890-2293-4b2d-8aa8-f6f0e8e92274",
"Error: instance and SSO URL params do not match",
"Error: instance and SSO URL params do not match\n",
],
[
"web",
"https://dashboard.gitguardian.com",
"https://dashboard.gitguardian.com",
"Error: Invalid value for sso-url: Please provide a valid SSO URL.",
"Error: Invalid value for sso-url: Please provide a valid SSO URL.\n",
],
[
"token",
"https://dashboard.gitguardian.com",
"https://dashboard.gitguardian.com/auth/sso/1e0f7890-2293-4b2d-8aa8-f6f0e8e92274",
"Error: Invalid value for sso-url: --sso-url is reserved for the web login method.",
"Error: Invalid value for sso-url: --sso-url is reserved for the web login method.\n",
],
],
)
Expand All @@ -887,6 +899,11 @@ def test_bad_sso_url(
exit_code, output = self.run_cmd(cli_fs_runner, method=method)
assert exit_code > 0, output
self._webbrowser_open_mock.assert_not_called()
if sys.version_info < (3, 9) and "Error:" not in expected_error:
expected_error += (
"Warning: Python 3.8 is no longer supported by the Python Software Foundation. "
"GGShield will soon require Python 3.9 or above to run.\n"
)
self._assert_last_print(output, expected_error)

@pytest.mark.parametrize(
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/cmd/auth/test_logout.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from typing import Optional, Tuple
from unittest.mock import Mock

Expand Down Expand Up @@ -81,6 +82,12 @@ def test_valid_logout(self, revoke, instance_url, monkeypatch, cli_fs_runner):
"from your configuration.\n"
)

if sys.version_info < (3, 9):
expected_output += (
"Warning: Python 3.8 is no longer supported by the Python Software Foundation. "
"GGShield will soon require Python 3.9 or above to run.\n"
)

assert output == expected_output

def test_logout_revoke_timeout(self, monkeypatch, cli_fs_runner):
Expand Down
10 changes: 9 additions & 1 deletion tests/unit/cmd/scan/test_docker.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import sys
from pathlib import Path
from unittest.mock import Mock, patch

Expand Down Expand Up @@ -75,7 +76,14 @@ def test_docker_scan_abort(
["-v", "secret", "scan", "docker", "ggshield-non-existant"],
)
assert_invoke_ok(result)
assert result.output == ""

expected_output = ""
if sys.version_info < (3, 9):
expected_output += (
"Warning: Python 3.8 is no longer supported by the Python Software Foundation. "
"GGShield will soon require Python 3.9 or above to run.\n"
)
assert result.output == expected_output

@patch("ggshield.cmd.secret.scan.docker.docker_save_to_tmp")
@patch("ggshield.cmd.secret.scan.docker.docker_scan_archive")
Expand Down
41 changes: 26 additions & 15 deletions tests/unit/cmd/test_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import sys
from datetime import datetime, timezone
from typing import Tuple

Expand Down Expand Up @@ -49,6 +50,15 @@
"""


def _check_expected_output(output: str, expected_output: str):
if sys.version_info < (3, 9) and "Error:" not in expected_output:
expected_output += (
"Warning: Python 3.8 is no longer supported by the Python Software Foundation. "
"GGShield will soon require Python 3.9 or above to run.\n"
)
assert output == expected_output


class TestConfigList:

@pytest.fixture
Expand Down Expand Up @@ -80,7 +90,7 @@ def test_valid_list(self, cli_fs_runner, setup_configs):
exit_code, output = self.run_cmd(cli_fs_runner)

assert exit_code == ExitCode.SUCCESS, output
assert output == EXPECTED_OUTPUT
_check_expected_output(output, EXPECTED_OUTPUT)

def test_list_json_output(
self, cli_fs_runner, config_list_json_schema, setup_configs
Expand All @@ -91,7 +101,6 @@ def test_list_json_output(
THEN all configs should be listed with the correct format
"""
exit_code_json, output_json = self.run_cmd(cli_fs_runner, json=True)

assert exit_code_json == ExitCode.SUCCESS, output_json
dct = json.loads(output_json)
jsonschema.validate(dct, config_list_json_schema)
Expand Down Expand Up @@ -123,6 +132,7 @@ def test_list_json_output(
@staticmethod
def run_cmd(cli_fs_runner, json: bool = False) -> Tuple[bool, str]:
cmd = ["config", "list", "--json"] if json else ["config", "list"]
cli_fs_runner.mix_stderr = False if json else True
result = cli_fs_runner.invoke(cli, cmd, color=False, catch_exceptions=False)
return result.exit_code, result.output

Expand All @@ -149,7 +159,7 @@ def test_set_lifetime_default_config_value(self, value, cli_fs_runner):
), "The instance config should remain unchanged"

assert exit_code == ExitCode.SUCCESS, output
assert output == ""
_check_expected_output(output, "")

@pytest.mark.parametrize("value", [0, 365])
def test_set_lifetime_instance_config_value(self, value, cli_fs_runner):
Expand Down Expand Up @@ -185,7 +195,7 @@ def test_set_lifetime_instance_config_value(self, value, cli_fs_runner):
), "The default auth config should remain unchanged"

assert exit_code == ExitCode.SUCCESS, output
assert output == ""
_check_expected_output(output, "")

def test_set_invalid_field_name(self, cli_fs_runner):
"""
Expand Down Expand Up @@ -236,8 +246,8 @@ def test_set_lifetime_invalid_instance(self, cli_fs_runner):

exit_code, output = self.run_cmd(cli_fs_runner, 0, instance_url=instance_url)

assert exit_code == ExitCode.AUTHENTICATION_ERROR, output
assert output == f"Error: Unknown instance: '{instance_url}'\n"
assert exit_code == ExitCode.AUTHENTICATION_ERROR
_check_expected_output(output, f"Error: Unknown instance: '{instance_url}'\n")

config = Config()
assert (
Expand Down Expand Up @@ -322,7 +332,7 @@ def test_unset_lifetime_instance_config_value(self, cli_fs_runner):
), "The default auth config should remain unchanged"

assert exit_code == ExitCode.SUCCESS, output
assert output == ""
_check_expected_output(output, "")

def test_unset_lifetime_default_config_value(self, cli_fs_runner):
"""
Expand All @@ -343,7 +353,7 @@ def test_unset_lifetime_default_config_value(self, cli_fs_runner):
), "Unrelated instance config should remain unchanged"

assert exit_code == ExitCode.SUCCESS, output
assert output == ""
_check_expected_output(output, "")

def test_unset_lifetime_all(self, cli_fs_runner):
"""
Expand All @@ -370,7 +380,7 @@ def test_unset_lifetime_all(self, cli_fs_runner):
assert config.auth_config.default_token_lifetime is None, output

assert exit_code == ExitCode.SUCCESS, output
assert output == ""
_check_expected_output(output, "")

def test_unset_lifetime_invalid_instance(self, cli_fs_runner):
"""
Expand All @@ -386,7 +396,7 @@ def test_unset_lifetime_invalid_instance(self, cli_fs_runner):
exit_code, output = self.run_cmd(cli_fs_runner, instance_url=instance_url)

assert exit_code == ExitCode.AUTHENTICATION_ERROR, output
assert output == f"Error: Unknown instance: '{instance_url}'\n"
_check_expected_output(output, f"Error: Unknown instance: '{instance_url}'\n")

config = Config()
assert (
Expand All @@ -409,7 +419,7 @@ def test_unset_instance(self, cli_fs_runner):
exit_code, output = self.run_cmd(cli_fs_runner, param="instance")

assert exit_code == ExitCode.SUCCESS, output
assert output == ""
_check_expected_output(output, "")

config, _ = UserConfig.load(config_path)
assert config.instance is None
Expand Down Expand Up @@ -461,7 +471,7 @@ def test_get_lifetime_default(

exit_code, output = self.run_cmd(cli_fs_runner)

assert output == f"default_token_lifetime: {expected_value}\n"
_check_expected_output(output, f"default_token_lifetime: {expected_value}\n")
assert exit_code == ExitCode.SUCCESS

@pytest.mark.parametrize(
Expand Down Expand Up @@ -497,7 +507,8 @@ def test_get_lifetime_instance(

exit_code, output = self.run_cmd(cli_fs_runner, instance_url=instance_url)

assert output == f"default_token_lifetime: {expected_value}\n"
expected_output = f"default_token_lifetime: {expected_value}\n"
_check_expected_output(output, expected_output)
assert exit_code == ExitCode.SUCCESS

def test_unset_lifetime_invalid_instance(self, cli_fs_runner):
Expand All @@ -510,7 +521,7 @@ def test_unset_lifetime_invalid_instance(self, cli_fs_runner):
exit_code, output = self.run_cmd(cli_fs_runner, instance_url=instance_url)

assert exit_code == ExitCode.AUTHENTICATION_ERROR, output
assert output == f"Error: Unknown instance: '{instance_url}'\n"
_check_expected_output(output, f"Error: Unknown instance: '{instance_url}'\n")

def test_get_invalid_field_name(self, cli_fs_runner):
"""
Expand Down Expand Up @@ -546,7 +557,7 @@ def test_get_instance(self, default_value, expected_value, cli_fs_runner):

exit_code, output = self.run_cmd(cli_fs_runner, param="instance")

assert output == f"instance: {expected_value}\n"
_check_expected_output(output, f"instance: {expected_value}\n")
assert exit_code == ExitCode.SUCCESS

@staticmethod
Expand Down
Loading
Loading