From 3ddddabac2fcc781f772dd3a9bcd0e9f94944f6d Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Mon, 9 Dec 2024 10:33:53 -0800 Subject: [PATCH 1/6] Support disabling telemetry with an environment variable (`AGENTSTACK_TELEMETRY_OPT_OUT`). Disable telemetry in test suite. Add a test for telemetry. #105 --- agentstack/utils.py | 9 ++++++++- tests/test_generation_files.py | 5 ----- tests/test_telemetry.py | 12 ++++++++++++ tox.ini | 4 +++- 4 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 tests/test_telemetry.py diff --git a/agentstack/utils.py b/agentstack/utils.py index de008489..612308e6 100644 --- a/agentstack/utils.py +++ b/agentstack/utils.py @@ -1,5 +1,5 @@ from typing import Optional -import sys +import os,sys import json from ruamel.yaml import YAML import re @@ -54,9 +54,16 @@ def get_framework(path: Optional[str] = None) -> str: def get_telemetry_opt_out(path: Optional[str] = None) -> bool: + """ + Gets the telemetry opt out setting. + First checks the environment variable AGENTSTACK_TELEMETRY_OPT_OUT. + If that is not set, it checks the agentstack.json file. + """ from agentstack.generation import ConfigFile try: + return(os.environ['AGENTSTACK_TELEMETRY_OPT_OUT']) + except KeyError: agentstack_config = ConfigFile(path) return bool(agentstack_config.telemetry_opt_out) except FileNotFoundError: diff --git a/tests/test_generation_files.py b/tests/test_generation_files.py index c5fae48b..a2baf5ef 100644 --- a/tests/test_generation_files.py +++ b/tests/test_generation_files.py @@ -66,11 +66,6 @@ def test_get_framework(self): with self.assertRaises(SystemExit) as _: get_framework(BASE_PATH / "missing") - def test_get_telemetry_opt_out(self): - assert get_telemetry_opt_out(BASE_PATH / "fixtures") is False - with self.assertRaises(SystemExit) as _: - get_telemetry_opt_out(BASE_PATH / "missing") - def test_read_env(self): env = EnvFile(BASE_PATH / "fixtures") assert env.variables == {"ENV_VAR1": "value1", "ENV_VAR2": "value2"} diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py new file mode 100644 index 00000000..8b7e32e5 --- /dev/null +++ b/tests/test_telemetry.py @@ -0,0 +1,12 @@ +import os +import unittest +from agentstack.utils import get_telemetry_opt_out + +class TestEnv(unittest.TestCase): + def test_telemetry_opt_out_env_var_set(self): + AGENTSTACK_TELEMETRY_OPT_OUT = os.getenv("AGENTSTACK_TELEMETRY_OPT_OUT") + assert AGENTSTACK_TELEMETRY_OPT_OUT + + def test_telemetry_opt_out_set_in_test_environment(self): + assert get_telemetry_opt_out() + diff --git a/tox.ini b/tox.ini index 6733c3ab..fc41e805 100644 --- a/tox.ini +++ b/tox.ini @@ -13,4 +13,6 @@ deps = mypy: mypy commands = pytest -v - mypy: mypy agentops \ No newline at end of file + mypy: mypy agentops +setenv = + AGENTSTACK_TELEMETRY_OPT_OUT = 1 \ No newline at end of file From 8da172ed8ab800f17ba18d6e4380c361aba3eb5f Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Mon, 9 Dec 2024 10:36:18 -0800 Subject: [PATCH 2/6] ruff format --- agentstack/utils.py | 4 ++-- tests/test_telemetry.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/agentstack/utils.py b/agentstack/utils.py index 612308e6..5ee6b4d9 100644 --- a/agentstack/utils.py +++ b/agentstack/utils.py @@ -1,5 +1,5 @@ from typing import Optional -import os,sys +import os, sys import json from ruamel.yaml import YAML import re @@ -62,7 +62,7 @@ def get_telemetry_opt_out(path: Optional[str] = None) -> bool: from agentstack.generation import ConfigFile try: - return(os.environ['AGENTSTACK_TELEMETRY_OPT_OUT']) + return os.environ['AGENTSTACK_TELEMETRY_OPT_OUT'] except KeyError: agentstack_config = ConfigFile(path) return bool(agentstack_config.telemetry_opt_out) diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index 8b7e32e5..375e3cb9 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -2,6 +2,7 @@ import unittest from agentstack.utils import get_telemetry_opt_out + class TestEnv(unittest.TestCase): def test_telemetry_opt_out_env_var_set(self): AGENTSTACK_TELEMETRY_OPT_OUT = os.getenv("AGENTSTACK_TELEMETRY_OPT_OUT") @@ -9,4 +10,3 @@ def test_telemetry_opt_out_env_var_set(self): def test_telemetry_opt_out_set_in_test_environment(self): assert get_telemetry_opt_out() - From 58087372b30fd3ac34829d29f77efc539754cd75 Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Mon, 9 Dec 2024 10:37:29 -0800 Subject: [PATCH 3/6] Lint, typing. --- agentstack/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agentstack/utils.py b/agentstack/utils.py index 5ee6b4d9..0bb32f93 100644 --- a/agentstack/utils.py +++ b/agentstack/utils.py @@ -62,7 +62,7 @@ def get_telemetry_opt_out(path: Optional[str] = None) -> bool: from agentstack.generation import ConfigFile try: - return os.environ['AGENTSTACK_TELEMETRY_OPT_OUT'] + return bool(os.environ['AGENTSTACK_TELEMETRY_OPT_OUT']) except KeyError: agentstack_config = ConfigFile(path) return bool(agentstack_config.telemetry_opt_out) From 818daa40fbf70917892b4b855c128c9797ea1a6f Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Mon, 9 Dec 2024 10:41:20 -0800 Subject: [PATCH 4/6] Better test classname --- tests/test_telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index 375e3cb9..35f3c5fa 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -3,7 +3,7 @@ from agentstack.utils import get_telemetry_opt_out -class TestEnv(unittest.TestCase): +class TelemetryTest(unittest.TestCase): def test_telemetry_opt_out_env_var_set(self): AGENTSTACK_TELEMETRY_OPT_OUT = os.getenv("AGENTSTACK_TELEMETRY_OPT_OUT") assert AGENTSTACK_TELEMETRY_OPT_OUT From 59e42d880dc98c4f86703b3e8db4bf84b39285c1 Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Mon, 9 Dec 2024 11:05:49 -0800 Subject: [PATCH 5/6] Load .env vars on project run instead of in project's main.py. Add `python-dotenv` to dependencies. Verify with tests. --- agentstack/cli/cli.py | 2 + .../src/main.py | 2 - pyproject.toml | 1 + tests/test_project_run.py | 46 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/test_project_run.py diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index 15b61676..dcbcbeb1 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -13,6 +13,7 @@ import os import importlib.resources from cookiecutter.main import cookiecutter +from dotenv import load_dotenv from .agentstack_data import ( FrameworkData, @@ -184,6 +185,7 @@ def run_project(framework: str, path: str = ''): print(e) sys.exit(1) + load_dotenv(_path / '.env') # explicitly load the project's .env file entrypoint = _path / frameworks.get_entrypoint_path(framework) os.system(f'python {entrypoint}') diff --git a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/main.py b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/main.py index bba6c84f..291a6177 100644 --- a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/main.py +++ b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/main.py @@ -2,8 +2,6 @@ import sys from crew import {{cookiecutter.project_metadata.project_name|replace('-', '')|replace('_', '')|capitalize}}Crew import agentops -from dotenv import load_dotenv -load_dotenv() agentops.init(default_tags=['crewai', 'agentstack']) diff --git a/pyproject.toml b/pyproject.toml index 1767a397..9c9f0ce4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "packaging==23.2", "requests>=2.32", "appdirs>=1.4.4", + "python-dotenv>=1.0.1", ] [project.optional-dependencies] diff --git a/tests/test_project_run.py b/tests/test_project_run.py new file mode 100644 index 00000000..787589da --- /dev/null +++ b/tests/test_project_run.py @@ -0,0 +1,46 @@ +import os +from pathlib import Path +import shutil +import unittest +from parameterized import parameterized_class + +from agentstack import frameworks +from agentstack.cli import run_project +from agentstack.generation.files import ConfigFile + +BASE_PATH = Path(__file__).parent + + +@parameterized_class([{"framework": framework} for framework in frameworks.SUPPORTED_FRAMEWORKS]) +class ProjectRunTest(unittest.TestCase): + def setUp(self): + self.project_dir = BASE_PATH / 'tmp' / self.framework + + os.makedirs(self.project_dir) + os.makedirs(self.project_dir / 'src') + (self.project_dir / 'src' / '__init__.py').touch() + + # set the framework in agentstack.json + shutil.copy(BASE_PATH / 'fixtures' / 'agentstack.json', self.project_dir / 'agentstack.json') + with ConfigFile(self.project_dir) as config: + config.framework = self.framework + + # populate the entrypoint + entrypoint_path = frameworks.get_entrypoint_path(self.framework, self.project_dir) + shutil.copy(BASE_PATH / f"fixtures/frameworks/{self.framework}/entrypoint_max.py", entrypoint_path) + + # write a basic .env file + shutil.copy(BASE_PATH / 'fixtures' / '.env', self.project_dir / '.env') + + def tearDown(self): + shutil.rmtree(self.project_dir) + + def test_run_project(self): + run_project(self.framework, self.project_dir) + + def test_env_is_set(self): + """ + After running a project, the environment variables should be set from project_dir/.env. + """ + run_project(self.framework, self.project_dir) + assert os.getenv('ENV_VAR1') == 'value1' From 3bb90479be6a678eb0e1dfc47f53b7bd35d1457b Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Mon, 9 Dec 2024 11:33:05 -0800 Subject: [PATCH 6/6] Explicitly pass environ to subprocess on run_project to gurarantee we share the same env. --- agentstack/cli/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index dcbcbeb1..bc5648c6 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -14,6 +14,7 @@ import importlib.resources from cookiecutter.main import cookiecutter from dotenv import load_dotenv +import subprocess from .agentstack_data import ( FrameworkData, @@ -187,7 +188,7 @@ def run_project(framework: str, path: str = ''): load_dotenv(_path / '.env') # explicitly load the project's .env file entrypoint = _path / frameworks.get_entrypoint_path(framework) - os.system(f'python {entrypoint}') + subprocess.run(['python', entrypoint], env=os.environ) def ask_framework() -> str: