diff --git a/docs/source/user_guide/installation/index.rst b/docs/source/user_guide/installation/index.rst
index 18e62c5dfa..9225f1ee98 100644
--- a/docs/source/user_guide/installation/index.rst
+++ b/docs/source/user_guide/installation/index.rst
@@ -71,6 +71,7 @@ Package Minimum supp
`typing-extensions `__ 4.10.0
`pandas `__ 1.5.0
`pooch `__ 1.8.1
+`posthog `__ 3.6.5
=================================================================== ==========================
.. _install.optional_dependencies:
diff --git a/noxfile.py b/noxfile.py
index 5ab32f463f..d65812b8ed 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -49,6 +49,7 @@ def set_iree_state():
"IREE_INDEX_URL": os.getenv(
"IREE_INDEX_URL", "https://iree.dev/pip-release-links.html"
),
+ "PYBAMM_DISABLE_TELEMETRY": "true",
}
VENV_DIR = Path("./venv").resolve()
diff --git a/src/pybamm/__init__.py b/src/pybamm/__init__.py
index c53d1a0e40..3eb267860a 100644
--- a/src/pybamm/__init__.py
+++ b/src/pybamm/__init__.py
@@ -203,7 +203,8 @@
import os
import pathlib
import sysconfig
-os.environ["CASADIPATH"] = str(pathlib.Path(sysconfig.get_path('purelib')) / 'casadi')
+
+os.environ["CASADIPATH"] = str(pathlib.Path(sysconfig.get_path("purelib")) / "casadi")
__all__ = [
"batch_study",
@@ -232,5 +233,4 @@
"pybamm_data",
]
-
pybamm.config.generate()
diff --git a/src/pybamm/config.py b/src/pybamm/config.py
index ee08ef6774..abe775ec97 100644
--- a/src/pybamm/config.py
+++ b/src/pybamm/config.py
@@ -3,8 +3,9 @@
import platformdirs
from pathlib import Path
import pybamm
-import select
import sys
+import threading
+import time
def is_running_tests(): # pragma: no cover
@@ -14,8 +15,6 @@ def is_running_tests(): # pragma: no cover
Returns:
bool: True if running tests or building docs, False otherwise.
"""
- import sys
-
# Check if pytest or unittest is running
if any(
test_module in sys.modules for test_module in ["pytest", "unittest", "nose"]
@@ -33,11 +32,14 @@ def is_running_tests(): # pragma: no cover
# Check for common test runner names in command-line arguments
test_runners = ["pytest", "unittest", "nose", "trial", "nox", "tox"]
- if any(runner in sys.argv[0].lower() for runner in test_runners):
+ if any(runner in arg.lower() for arg in sys.argv for runner in test_runners):
return True
# Check if building docs with Sphinx
- if "sphinx" in sys.modules:
+ if any(mod == "sphinx" or mod.startswith("sphinx.") for mod in sys.modules):
+ print(
+ f"Found Sphinx module: {[mod for mod in sys.modules if mod.startswith('sphinx')]}"
+ )
return True
return False
@@ -67,24 +69,47 @@ def ask_user_opt_in(timeout=10):
"For more information, see https://docs.pybamm.org/en/latest/source/user_guide/index.html#telemetry"
)
+ def get_input():
+ try:
+ user_input = (
+ input("Do you want to enable telemetry? (Y/n): ").strip().lower()
+ )
+ answer.append(user_input)
+ except Exception:
+ # Handle any input errors
+ pass
+
+ time_start = time.time()
+
while True:
- print("Do you want to enable telemetry? (Y/n): ", end="", flush=True)
+ if time.time() - time_start > timeout:
+ print("\nTimeout reached. Defaulting to not enabling telemetry.")
+ return False
+
+ answer = []
+ # Create and start input thread
+ input_thread = threading.Thread(target=get_input)
+ input_thread.daemon = True
+ input_thread.start()
+
+ # Wait for either timeout or input
+ input_thread.join(timeout)
- rlist, _, _ = select.select([sys.stdin], [], [], timeout)
- if rlist:
- user_input = sys.stdin.readline().strip().lower()
- if user_input in ["yes", "y", ""]:
+ if answer:
+ if answer[0] in ["yes", "y", ""]:
+ print("\nTelemetry enabled.\n")
return True
- elif user_input in ["no", "n"]:
+ elif answer[0] in ["no", "n"]:
+ print("\nTelemetry disabled.\n")
return False
else:
- print("Invalid input. Please enter 'yes/y' for yes or 'no/n' for no.")
+ print("\nInvalid input. Please enter 'yes/y' for yes or 'no/n' for no.")
else:
print("\nTimeout reached. Defaulting to not enabling telemetry.")
return False
-def generate(): # pragma: no cover
+def generate():
if is_running_tests():
return
@@ -101,7 +126,7 @@ def generate(): # pragma: no cover
pybamm.telemetry.capture("user-opted-in")
-def read(): # pragma: no cover
+def read():
config_file = Path(platformdirs.user_config_dir("pybamm")) / "config.yml"
return read_uuid_from_file(config_file)
@@ -121,7 +146,7 @@ def write_uuid_to_file(config_file, opt_in):
def read_uuid_from_file(config_file):
# Check if the config file exists
- if not config_file.exists(): # pragma: no cover
+ if not config_file.exists():
return None
# Read the UUID from the config file
@@ -134,5 +159,5 @@ def read_uuid_from_file(config_file):
config = yaml.safe_load(content)
return config["pybamm"]
- except (yaml.YAMLError, ValueError): # pragma: no cover
+ except (yaml.YAMLError, ValueError):
return None
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
index 65d42bf168..62906b348d 100644
--- a/tests/unit/test_config.py
+++ b/tests/unit/test_config.py
@@ -4,6 +4,8 @@
import pybamm
import uuid
+from pathlib import Path
+import platformdirs
class TestConfig:
@@ -104,3 +106,52 @@ def mock_select(*args, **kwargs):
captured = capsys.readouterr()
assert "Do you want to enable telemetry? (Y/n):" in captured.out
assert "Timeout reached. Defaulting to not enabling telemetry." in captured.out
+
+ def test_generate_and_read(self, monkeypatch, tmp_path):
+ # Mock is_running_tests to return False
+ monkeypatch.setattr(pybamm.config, "is_running_tests", lambda: False)
+
+ # Mock ask_user_opt_in to return True
+ monkeypatch.setattr(pybamm.config, "ask_user_opt_in", lambda: True)
+
+ # Mock telemetry capture
+ capture_called = False
+
+ def mock_capture(event):
+ nonlocal capture_called
+ assert event == "user-opted-in"
+ capture_called = True
+
+ monkeypatch.setattr(pybamm.telemetry, "capture", mock_capture)
+
+ # Mock config directory
+ monkeypatch.setattr(platformdirs, "user_config_dir", lambda x: str(tmp_path))
+
+ # Test generate() creates new config
+ pybamm.config.generate()
+
+ # Verify config was created
+ config = pybamm.config.read()
+ assert config is not None
+ assert config["enable_telemetry"] is True
+ assert "uuid" in config
+ assert capture_called is True
+
+ # Test generate() does nothing if config exists
+ capture_called = False
+ pybamm.config.generate()
+ assert capture_called is False
+
+ def test_read_uuid_from_file_no_file(self):
+ config_dict = pybamm.config.read_uuid_from_file(Path("nonexistent_file.yml"))
+ assert config_dict is None
+
+ def test_read_uuid_from_file_invalid_yaml(self, tmp_path):
+ # Create a temporary directory and file with invalid YAML content
+ invalid_yaml = tmp_path / "invalid_yaml.yml"
+ with open(invalid_yaml, "w") as f:
+ f.write("invalid: yaml: content:")
+
+ config_dict = pybamm.config.read_uuid_from_file(invalid_yaml)
+
+ assert config_dict is None