diff --git a/docs/source/user_guide/index.md b/docs/source/user_guide/index.md index 11d1e12ff7..f61d2fc253 100644 --- a/docs/source/user_guide/index.md +++ b/docs/source/user_guide/index.md @@ -75,9 +75,9 @@ glob: # Telemetry -PyBaMM collects anonymous usage data to help improve the library. This telemetry is enabled by default but can be easily disabled. Here's what you need to know: +PyBaMM optionally collects anonymous usage data to help improve the library. This telemetry is opt-in and can be easily disabled. Here's what you need to know: - **What is collected**: Basic usage information like PyBaMM version, Python version, and which functions are run. - **Why**: To understand how PyBaMM is used and prioritize development efforts. -- **Opt-out**: To disable telemetry, set the environment variable `PYBAMM_OPTOUT_TELEMETRY=true` or use `pybamm.telemetry.disable()` in your code. +- **Opt-out**: To disable telemetry, set the environment variable `PYBAMM_DISABLE_TELEMETRY=true` (or any value other than `false`) or use `pybamm.telemetry.disable()` in your code. - **Privacy**: No personal information (name, email, etc) or sensitive information (parameter values, simulation results, etc) is ever collected. diff --git a/examples/scripts/compare_lithium_ion.py b/examples/scripts/compare_lithium_ion.py index 4764778c34..6108036b9b 100644 --- a/examples/scripts/compare_lithium_ion.py +++ b/examples/scripts/compare_lithium_ion.py @@ -4,7 +4,6 @@ import pybamm pybamm.set_logging_level("INFO") -pybamm.telemetry.disable() # load models models = [ diff --git a/scripts/post_install.py b/scripts/post_install.py deleted file mode 100644 index f9bd54e316..0000000000 --- a/scripts/post_install.py +++ /dev/null @@ -1,5 +0,0 @@ -import pybamm - - -def run(): - pybamm.config.generate() diff --git a/src/pybamm/__init__.py b/src/pybamm/__init__.py index 3a6f7eeddd..643248f5c7 100644 --- a/src/pybamm/__init__.py +++ b/src/pybamm/__init__.py @@ -227,3 +227,6 @@ "version", "pybamm_data", ] + + +pybamm.config.generate() diff --git a/src/pybamm/config.py b/src/pybamm/config.py index e60ad1d5d2..9e3029851b 100644 --- a/src/pybamm/config.py +++ b/src/pybamm/config.py @@ -2,6 +2,7 @@ import os import platformdirs from pathlib import Path +import pybamm def is_running_tests(): # pragma: no cover @@ -33,6 +34,34 @@ def is_running_tests(): # pragma: no cover return any(runner in sys.argv[0].lower() for runner in test_runners) +def ask_user_opt_in(): + """ + Ask the user if they want to opt in to telemetry. + + Returns + ------- + bool + True if the user opts in, False otherwise. + """ + print( + "PyBaMM can collect usage data and send it to the PyBaMM team to " + "help us improve the software.\n" + "We do not collect any sensitive information such as models, parameters, " + "or simulation results - only information on which parts of the code are " + "being used and how frequently.\n" + "This is entirely optional and does not impact the functionality of PyBaMM.\n" + "For more information, see https://docs.pybamm.org/en/latest/source/user_guide/index.html#telemetry" + ) + while True: + user_input = input("Do you want to enable telemetry? (Y/n): ").strip().lower() + if user_input in ["yes", "y"]: + return True + elif user_input in ["no", "n"]: + return False + else: + print("\nInvalid input. Please enter 'yes'/'y' or 'no'/'n'.") + + def generate(): # pragma: no cover if is_running_tests(): return @@ -41,8 +70,13 @@ def generate(): # pragma: no cover if read() is not None: return + # Ask the user if they want to opt in to telemetry + opt_in = ask_user_opt_in() config_file = Path(platformdirs.user_config_dir("pybamm")) / "config.yml" - write_uuid_to_file(config_file) + write_uuid_to_file(config_file, opt_in) + + if opt_in: + pybamm.telemetry.capture("user-opted-in") def read(): # pragma: no cover @@ -50,17 +84,17 @@ def read(): # pragma: no cover return read_uuid_from_file(config_file) -def write_uuid_to_file(config_file): +def write_uuid_to_file(config_file, opt_in): # Create the directory if it doesn't exist config_file.parent.mkdir(parents=True, exist_ok=True) - # Generate a UUID - unique_id = uuid.uuid4() - # Write the UUID to the config file in YAML format with open(config_file, "w") as f: f.write("pybamm:\n") - f.write(f" uuid: {unique_id}\n") + f.write(f" enable_telemetry: {opt_in}\n") + if opt_in: + unique_id = uuid.uuid4() + f.write(f" uuid: {unique_id}\n") def read_uuid_from_file(config_file): diff --git a/src/pybamm/telemetry.py b/src/pybamm/telemetry.py index 885249d22f..af91deca9d 100644 --- a/src/pybamm/telemetry.py +++ b/src/pybamm/telemetry.py @@ -14,14 +14,14 @@ def disable(): _posthog.disabled = True -_opt_out = os.getenv("PYBAMM_OPTOUT_TELEMETRY", "false").lower() +_opt_out = os.getenv("PYBAMM_DISABLE_TELEMETRY", "false").lower() if _opt_out != "false": # pragma: no cover disable() def capture(event): # pragma: no cover # don't capture events in automated testing - if pybamm.config.is_running_tests(): + if pybamm.config.is_running_tests() or _posthog.disabled: return properties = { @@ -31,11 +31,6 @@ def capture(event): # pragma: no cover config = pybamm.config.read() if config: - user_id = config["uuid"] - else: - user_id = "anonymous-user-id" - properties["$process_person_profile"] = False - - # setting $process_person_profile to False mean that we only track what events are - # being run and don't capture anything about the user - _posthog.capture(user_id, event, properties=properties) + if config["enable_telemetry"]: + user_id = config["uuid"] + _posthog.capture(user_id, event, properties=properties) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 45c1260c09..68610f48ee 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -5,26 +5,38 @@ class TestConfig: - def test_write_read_uuid(self, tmp_path): + @pytest.mark.parametrize("write_opt_in", [True, False]) + def test_write_read_uuid(self, tmp_path, write_opt_in): # Create a temporary file path config_file = tmp_path / "config.yml" # Call the function to write UUID to file - pybamm.config.write_uuid_to_file(config_file) + pybamm.config.write_uuid_to_file(config_file, write_opt_in) # Check that the file was created assert config_file.exists() # Read the UUID using the read_uuid_from_file function - uuid_dict = pybamm.config.read_uuid_from_file(config_file) - + config_dict = pybamm.config.read_uuid_from_file(config_file) # Check that the UUID was read successfully - assert uuid_dict is not None - assert "uuid" in uuid_dict - - # Verify that the UUID is valid - - try: - uuid.UUID(uuid_dict["uuid"]) - except ValueError: - pytest.fail("Invalid UUID format") + if write_opt_in: + assert config_dict["enable_telemetry"] is True + assert "uuid" in config_dict + + # Verify that the UUID is valid + try: + uuid.UUID(config_dict["uuid"]) + except ValueError: + pytest.fail("Invalid UUID format") + else: + assert config_dict["enable_telemetry"] is False + + def test_ask_user_opt_in(self, monkeypatch): + # Mock the input function to always return "y" + monkeypatch.setattr("builtins.input", lambda _: "y") + + # Call the function to ask the user if they want to opt in + opt_in = pybamm.config.ask_user_opt_in() + + # Check that the function returns True + assert opt_in is True