ostruct-cli 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,248 @@
1
+ """Click command and options for the CLI.
2
+
3
+ This module contains all Click-related code separated from the main CLI logic.
4
+ We isolate this code here and disable mypy type checking for the entire module
5
+ because Click's decorator-based API is not easily type-checkable, leading to
6
+ many type: ignore comments in the main code.
7
+ """
8
+
9
+ # mypy: ignore-errors
10
+ # ^ This tells mypy to ignore type checking for this entire file
11
+
12
+ from typing import Any, Callable
13
+
14
+ import click
15
+
16
+ from ostruct import __version__
17
+ from ostruct.cli.errors import ( # noqa: F401 - Used in error handling
18
+ SystemPromptError,
19
+ TaskTemplateVariableError,
20
+ )
21
+
22
+
23
+ def validate_task_params(
24
+ ctx: click.Context, param: click.Parameter, value: Any
25
+ ) -> Any:
26
+ """Validate task-related parameters."""
27
+ if not hasattr(ctx, "params"):
28
+ return value
29
+
30
+ # Check for conflicting task parameters
31
+ if (
32
+ param.name == "task_file"
33
+ and value is not None
34
+ and ctx.params.get("task") is not None
35
+ ):
36
+ raise click.UsageError("Cannot specify both --task and --task-file")
37
+ elif (
38
+ param.name == "task"
39
+ and value is not None
40
+ and ctx.params.get("task_file") is not None
41
+ ):
42
+ raise click.UsageError("Cannot specify both --task and --task-file")
43
+
44
+ return value
45
+
46
+
47
+ def validate_system_prompt_params(
48
+ ctx: click.Context, param: click.Parameter, value: Any
49
+ ) -> Any:
50
+ """Validate system prompt parameters."""
51
+ if not hasattr(ctx, "params"):
52
+ return value
53
+
54
+ # Check for conflicting system prompt parameters
55
+ if (
56
+ param.name == "system_prompt_file"
57
+ and value is not None
58
+ and ctx.params.get("system_prompt") is not None
59
+ ):
60
+ raise click.UsageError(
61
+ "Cannot specify both --system-prompt and --system-prompt-file"
62
+ )
63
+ elif (
64
+ param.name == "system_prompt"
65
+ and value is not None
66
+ and ctx.params.get("system_prompt_file") is not None
67
+ ):
68
+ raise click.UsageError(
69
+ "Cannot specify both --system-prompt and --system-prompt-file"
70
+ )
71
+
72
+ return value
73
+
74
+
75
+ def debug_options(f: Callable) -> Callable:
76
+ """Add debug-related CLI options."""
77
+ f = click.option(
78
+ "--show-model-schema",
79
+ is_flag=True,
80
+ help="Show generated Pydantic model schema",
81
+ )(f)
82
+ f = click.option(
83
+ "--debug-validation",
84
+ is_flag=True,
85
+ help="Show detailed validation errors",
86
+ )(f)
87
+ return f
88
+
89
+
90
+ def file_options(f: Callable) -> Callable:
91
+ """Add file-related CLI options."""
92
+ f = click.option(
93
+ "--file", "-f", multiple=True, help="File mapping (name=path)"
94
+ )(f)
95
+ f = click.option(
96
+ "--files",
97
+ multiple=True,
98
+ help="Multiple file mappings from a directory",
99
+ )(f)
100
+ f = click.option(
101
+ "--dir", "-d", multiple=True, help="Directory mapping (name=path)"
102
+ )(f)
103
+ f = click.option(
104
+ "--allowed-dir",
105
+ multiple=True,
106
+ help="Additional allowed directory paths",
107
+ )(f)
108
+ f = click.option(
109
+ "--base-dir", type=str, help="Base directory for relative paths"
110
+ )(f)
111
+ f = click.option(
112
+ "--allowed-dir-file",
113
+ type=str,
114
+ help="File containing allowed directory paths",
115
+ )(f)
116
+ f = click.option(
117
+ "--dir-recursive", is_flag=True, help="Recursively process directories"
118
+ )(f)
119
+ f = click.option(
120
+ "--dir-ext", type=str, help="Filter directory files by extension"
121
+ )(f)
122
+ return f
123
+
124
+
125
+ def variable_options(f: Callable) -> Callable:
126
+ """Add variable-related CLI options."""
127
+ f = click.option(
128
+ "--var", "-v", multiple=True, help="Variable mapping (name=value)"
129
+ )(f)
130
+ f = click.option(
131
+ "--json-var",
132
+ "-j",
133
+ multiple=True,
134
+ help="JSON variable mapping (name=json_value)",
135
+ )(f)
136
+ return f
137
+
138
+
139
+ def model_options(f: Callable) -> Callable:
140
+ """Add model-related CLI options."""
141
+ f = click.option(
142
+ "--model", type=str, default="gpt-4o", help="OpenAI model to use"
143
+ )(f)
144
+ f = click.option(
145
+ "--temperature", type=float, default=0.0, help="Sampling temperature"
146
+ )(f)
147
+ f = click.option(
148
+ "--max-tokens", type=int, help="Maximum tokens in response"
149
+ )(f)
150
+ f = click.option(
151
+ "--top-p", type=float, default=1.0, help="Nucleus sampling threshold"
152
+ )(f)
153
+ f = click.option(
154
+ "--frequency-penalty",
155
+ type=float,
156
+ default=0.0,
157
+ help="Frequency penalty",
158
+ )(f)
159
+ f = click.option(
160
+ "--presence-penalty", type=float, default=0.0, help="Presence penalty"
161
+ )(f)
162
+ return f
163
+
164
+
165
+ def create_click_command() -> Callable:
166
+ """Create the Click command with all options."""
167
+
168
+ def decorator(f: Callable) -> Callable:
169
+ f = click.command(help="Make structured OpenAI API calls.")(f)
170
+ f = click.option(
171
+ "--task",
172
+ help="Task template string",
173
+ type=str,
174
+ callback=validate_task_params,
175
+ )(f)
176
+ f = click.option(
177
+ "--task-file",
178
+ help="Task template file path",
179
+ type=str,
180
+ callback=validate_task_params,
181
+ )(f)
182
+ f = click.option(
183
+ "--system-prompt",
184
+ help="System prompt string",
185
+ type=str,
186
+ callback=validate_system_prompt_params,
187
+ )(f)
188
+ f = click.option(
189
+ "--system-prompt-file",
190
+ help="System prompt file path",
191
+ type=str,
192
+ callback=validate_system_prompt_params,
193
+ )(f)
194
+ f = click.option(
195
+ "--schema-file",
196
+ required=True,
197
+ help="JSON schema file for response validation",
198
+ type=str,
199
+ )(f)
200
+ f = click.option(
201
+ "--ignore-task-sysprompt",
202
+ is_flag=True,
203
+ help="Ignore system prompt from task template YAML frontmatter",
204
+ )(f)
205
+ f = click.option(
206
+ "--timeout",
207
+ type=float,
208
+ default=60.0,
209
+ help="API timeout in seconds",
210
+ )(f)
211
+ f = click.option(
212
+ "--output-file", help="Write JSON output to file", type=str
213
+ )(f)
214
+ f = click.option(
215
+ "--dry-run",
216
+ is_flag=True,
217
+ help="Simulate API call without making request",
218
+ )(f)
219
+ f = click.option(
220
+ "--no-progress", is_flag=True, help="Disable progress indicators"
221
+ )(f)
222
+ f = click.option(
223
+ "--progress-level",
224
+ type=click.Choice(["none", "basic", "detailed"]),
225
+ default="basic",
226
+ help="Progress reporting level",
227
+ )(f)
228
+ f = click.option(
229
+ "--api-key", help="OpenAI API key (overrides env var)", type=str
230
+ )(f)
231
+ f = click.option(
232
+ "--verbose",
233
+ is_flag=True,
234
+ help="Enable verbose output and detailed logging",
235
+ )(f)
236
+ f = click.option(
237
+ "--debug-openai-stream",
238
+ is_flag=True,
239
+ help="Enable low-level debug output for OpenAI streaming",
240
+ )(f)
241
+ f = debug_options(f)
242
+ f = file_options(f)
243
+ f = variable_options(f)
244
+ f = model_options(f)
245
+ f = click.version_option(version=__version__)(f)
246
+ return f
247
+
248
+ return decorator