11from __future__ import annotations
22
3+ import os
34import sys
45from pathlib import Path
6+ from typing import Callable
57
68import click
79import questionary
810
911from ._main_utils import cli_action , cli_bold , cli_code , path_rel_wd
1012
1113
12- def generate_test_file (
13- * ,
14- app_file : str | None ,
15- output_file : str | None ,
16- provider : str ,
17- model : str | None ,
18- ):
19- """Generate AI-powered test file for a Shiny app."""
20-
21- if app_file is None :
22-
23- def path_exists (x : str ) -> bool | str :
24- if not isinstance (x , (str , Path )):
25- return False
26- path = Path (x )
27- if path .is_dir ():
28- return "Please provide a file path to your Shiny app"
29- return path .exists () or f"Shiny app file can not be found: { x } "
30-
31- app_file_val = questionary .path (
32- "Enter the path to the app file:" ,
33- default = path_rel_wd ("app.py" ),
34- validate = path_exists ,
35- ).ask ()
36- else :
37- app_file_val = app_file
14+ class ValidationError (Exception ):
15+ pass
16+
17+
18+ def create_file_validator (
19+ file_type : str ,
20+ must_exist : bool = True ,
21+ prefix_required : str | None = None ,
22+ must_not_exist : bool = False ,
23+ ) -> Callable [[str ], bool | str ]:
24+ def validator (path_str : str ) -> bool | str :
25+ if not isinstance (path_str , (str , Path )):
26+ return False
27+
28+ path = Path (path_str )
29+
30+ if path .is_dir ():
31+ return f"Please provide a file path for your { file_type } "
32+
33+ if must_exist and not path .exists ():
34+ return f"{ file_type .title ()} file not found: { path_str } "
35+
36+ if must_not_exist and path .exists ():
37+ return f"{ file_type .title ()} file already exists. Please provide a new file name."
38+
39+ if prefix_required and not path .name .startswith (prefix_required ):
40+ return f"{ file_type .title ()} file must start with '{ prefix_required } '"
41+
42+ return True
43+
44+ return validator
45+
46+
47+ def validate_api_key (provider : str ) -> None :
48+ api_configs = {
49+ "anthropic" : {
50+ "env_var" : "ANTHROPIC_API_KEY" ,
51+ "url" : "https://console.anthropic.com/" ,
52+ },
53+ "openai" : {
54+ "env_var" : "OPENAI_API_KEY" ,
55+ "url" : "https://platform.openai.com/api-keys" ,
56+ },
57+ }
58+
59+ if provider not in api_configs :
60+ raise ValidationError (f"Unsupported provider: { provider } " )
61+
62+ config = api_configs [provider ]
63+ if not os .getenv (config ["env_var" ]):
64+ raise ValidationError (
65+ f"{ config ['env_var' ]} environment variable is not set.\n "
66+ f"Please set your { provider .title ()} API key:\n "
67+ f" export { config ['env_var' ]} ='your-api-key-here'\n \n "
68+ f"Get your API key from: { config ['url' ]} "
69+ )
70+
71+
72+ def get_app_file_path (app_file : str | None ) -> Path :
73+ if app_file is not None :
74+ app_path = Path (app_file )
75+ if not app_path .exists ():
76+ raise ValidationError (f"App file does not exist: { app_path } " )
77+ return app_path
78+ # Interactive mode
79+ app_file_val = questionary .path (
80+ "Enter the path to the app file:" ,
81+ default = path_rel_wd ("app.py" ),
82+ validate = create_file_validator ("Shiny app" , must_exist = True ),
83+ ).ask ()
3884
3985 if app_file_val is None :
4086 sys .exit (1 )
4187
42- app_path = Path (app_file_val )
88+ return Path (app_file_val )
4389
44- if not app_path .exists ():
45- click .echo (f"❌ Error: App file does not exist: { app_path } " )
46- sys .exit (1 )
4790
48- if output_file is None :
49- suggested_output = app_path .parent / f"test_{ app_path .stem } .py"
50-
51- def output_path_valid (x : str ) -> bool | str :
52- if not isinstance (x , (str , Path )):
53- return False
54- path = Path (x )
55- if path .is_dir ():
56- return "Please provide a file path for your test file."
57- if path .exists ():
58- return "Test file already exists. Please provide a new file name."
59- if not path .name .startswith ("test_" ):
60- return "Test file must start with 'test_'"
61- return True
62-
63- output_file_val = questionary .path (
64- "Enter the path for the generated test file:" ,
65- default = str (suggested_output ),
66- validate = output_path_valid ,
67- ).ask ()
68- else :
69- output_file_val = output_file
91+ def get_output_file_path (output_file : str | None , app_path : Path ) -> Path :
92+ if output_file is not None :
93+ output_path = Path (output_file )
94+ if output_path .exists ():
95+ raise ValidationError (f"Test file already exists: { output_path } " )
96+ if not output_path .name .startswith ("test_" ):
97+ raise ValidationError ("Test file must start with 'test_'" )
98+ return output_path
99+ # Interactive mode
100+ suggested_output = app_path .parent / f"test_{ app_path .stem } .py"
101+
102+ output_file_val = questionary .path (
103+ "Enter the path for the generated test file:" ,
104+ default = str (suggested_output ),
105+ validate = create_file_validator (
106+ "test" , must_exist = False , prefix_required = "test_" , must_not_exist = True
107+ ),
108+ ).ask ()
70109
71110 if output_file_val is None :
72111 sys .exit (1 )
73112
74- output_path = Path (output_file_val )
113+ return Path (output_file_val )
75114
76- if output_path .exists ():
77- click .echo (f"❌ Error: Test file already exists: { output_path } " )
78- sys .exit (1 )
79115
80- if not output_path .name .startswith ("test_" ):
81- click .echo ("❌ Error: Test file must start with 'test_'" )
82- sys .exit (1 )
116+ def generate_test_file (
117+ * ,
118+ app_file : str | None ,
119+ output_file : str | None ,
120+ provider : str ,
121+ model : str | None ,
122+ ) -> None :
83123
84124 try :
85- from .testing import ShinyTestGenerator
86- except ImportError as e :
87- click .echo (f"❌ Error: Could not import ShinyTestGenerator: { e } " )
88- click .echo ("Make sure the shiny testing dependencies are installed." )
89- sys .exit (1 )
125+ validate_api_key (provider )
90126
91- import os
92-
93- if provider == "anthropic" :
94- if not os .getenv ("ANTHROPIC_API_KEY" ):
95- click .echo ("❌ Error: ANTHROPIC_API_KEY environment variable is not set." )
96- click .echo ("Please set your Anthropic API key:" )
97- click .echo (" export ANTHROPIC_API_KEY='your-api-key-here'" )
98- click .echo ()
99- click .echo ("Get your API key from: https://console.anthropic.com/" )
100- sys .exit (1 )
101- elif provider == "openai" :
102- if not os .getenv ("OPENAI_API_KEY" ):
103- click .echo ("❌ Error: OPENAI_API_KEY environment variable is not set." )
104- click .echo ("Please set your OpenAI API key:" )
105- click .echo (" export OPENAI_API_KEY='your-api-key-here'" )
106- click .echo ()
107- click .echo ("Get your API key from: https://platform.openai.com/api-keys" )
108- sys .exit (1 )
109-
110- click .echo (f"🤖 Generating test using { provider } provider..." )
111- if model :
112- click .echo (f"📝 Using model: { model } " )
127+ app_path = get_app_file_path (app_file )
128+ output_path = get_output_file_path (output_file , app_path )
113129
114- try :
115- generator = ShinyTestGenerator (provider = provider ) # type: ignore
130+ try :
131+ from .testing import ShinyTestGenerator
132+ except ImportError as e :
133+ raise ValidationError (
134+ f"Could not import ShinyTestGenerator: { e } \n "
135+ "Make sure the shiny testing dependencies are installed."
136+ )
137+
138+ click .echo (f"🤖 Generating test using { provider } provider..." )
139+ if model :
140+ click .echo (f"📝 Using model: { model } " )
116141
117- # Generate the test
142+ generator = ShinyTestGenerator ( provider = provider ) # type: ignore
118143 _ , test_file_path = generator .generate_test_from_file (
119144 app_file_path = str (app_path ),
120145 model = model ,
@@ -129,6 +154,9 @@ def output_path_valid(x: str) -> bool | str:
129154 )
130155 click .echo ("- Review and customize the test as needed" )
131156
157+ except ValidationError as e :
158+ click .echo (f"❌ Error: { e } " )
159+ sys .exit (1 )
132160 except Exception as e :
133161 click .echo (f"❌ Error generating test: { e } " )
134162 sys .exit (1 )
0 commit comments