ostruct-cli 0.4.0__tar.gz → 0.5.0__tar.gz
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.
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/PKG-INFO +60 -21
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/README.md +56 -16
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/pyproject.toml +25 -19
- ostruct_cli-0.5.0/src/ostruct/cli/base_errors.py +183 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/cli.py +822 -543
- ostruct_cli-0.5.0/src/ostruct/cli/click_options.py +375 -0
- ostruct_cli-0.5.0/src/ostruct/cli/errors.py +474 -0
- ostruct_cli-0.5.0/src/ostruct/cli/exit_codes.py +18 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/file_info.py +30 -14
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/file_list.py +4 -10
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/file_utils.py +43 -35
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/path_utils.py +32 -4
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/allowed_checker.py +8 -0
- ostruct_cli-0.5.0/src/ostruct/cli/security/base.py +46 -0
- ostruct_cli-0.5.0/src/ostruct/cli/security/errors.py +164 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/security_manager.py +22 -9
- ostruct_cli-0.5.0/src/ostruct/cli/serialization.py +25 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/template_filters.py +5 -3
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/template_rendering.py +46 -22
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/template_utils.py +12 -4
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/template_validation.py +26 -8
- ostruct_cli-0.5.0/src/ostruct/cli/token_utils.py +43 -0
- ostruct_cli-0.5.0/src/ostruct/cli/validators.py +109 -0
- ostruct_cli-0.4.0/src/ostruct/cli/click_options.py +0 -257
- ostruct_cli-0.4.0/src/ostruct/cli/errors.py +0 -380
- ostruct_cli-0.4.0/src/ostruct/cli/security/errors.py +0 -184
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/LICENSE +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/__init__.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/__init__.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/cache_manager.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/progress.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/__init__.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/case_manager.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/normalization.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/safe_joiner.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/symlink_resolver.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/types.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/security/windows_paths.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/template_env.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/template_extensions.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/template_io.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/template_schema.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/cli/utils.py +0 -0
- {ostruct_cli-0.4.0 → ostruct_cli-0.5.0}/src/ostruct/py.typed +0 -0
@@ -1,12 +1,11 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ostruct-cli
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Summary: CLI for OpenAI Structured Output
|
5
5
|
Author: Yaniv Golan
|
6
6
|
Author-email: yaniv@golan.name
|
7
|
-
Requires-Python: >=3.
|
7
|
+
Requires-Python: >=3.10,<4.0
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
10
9
|
Classifier: Programming Language :: Python :: 3.10
|
11
10
|
Classifier: Programming Language :: Python :: 3.11
|
12
11
|
Classifier: Programming Language :: Python :: 3.12
|
@@ -17,10 +16,10 @@ Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
17
16
|
Requires-Dist: ijson (>=3.2.3,<4.0.0)
|
18
17
|
Requires-Dist: jsonschema (>=4.23.0,<5.0.0)
|
19
18
|
Requires-Dist: openai (>=1.0.0,<2.0.0)
|
20
|
-
Requires-Dist: openai-structured (>=
|
19
|
+
Requires-Dist: openai-structured (>=2.0.0,<3.0.0)
|
21
20
|
Requires-Dist: pydantic (>=2.6.3,<3.0.0)
|
22
21
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
23
|
-
Requires-Dist: tiktoken (>=0.
|
22
|
+
Requires-Dist: tiktoken (>=0.9.0,<0.10.0)
|
24
23
|
Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
|
25
24
|
Requires-Dist: typing-extensions (>=4.9.0,<5.0.0)
|
26
25
|
Requires-Dist: werkzeug (>=3.1.3,<4.0.0)
|
@@ -91,7 +90,16 @@ Extract information about the person: {{ stdin }}
|
|
91
90
|
4. Run the CLI:
|
92
91
|
|
93
92
|
```bash
|
94
|
-
|
93
|
+
ostruct run task.j2 schema.json
|
94
|
+
```
|
95
|
+
|
96
|
+
Or with more options:
|
97
|
+
|
98
|
+
```bash
|
99
|
+
ostruct run task.j2 schema.json \
|
100
|
+
-f content input.txt \
|
101
|
+
-m gpt-4o \
|
102
|
+
--sys-prompt "You are an expert content analyzer"
|
95
103
|
```
|
96
104
|
|
97
105
|
Output:
|
@@ -112,15 +120,56 @@ Template files use the `.j2` extension to indicate they contain Jinja2 template
|
|
112
120
|
- Makes it clear the file contains template logic
|
113
121
|
- Follows industry standards for Jinja2 templates
|
114
122
|
|
115
|
-
|
123
|
+
## CLI Options
|
124
|
+
|
125
|
+
The CLI revolves around a single subcommand called `run`. Basic usage:
|
126
|
+
|
127
|
+
```bash
|
128
|
+
ostruct run <TASK_TEMPLATE> <SCHEMA_FILE> [OPTIONS]
|
129
|
+
```
|
130
|
+
|
131
|
+
Common options include:
|
132
|
+
|
133
|
+
- File & Directory Inputs:
|
134
|
+
- `-f <NAME> <PATH>`: Map a single file to a variable name
|
135
|
+
- `-d <NAME> <DIR>`: Map a directory to a variable name
|
136
|
+
- `-p <NAME> <PATTERN>`: Map files matching a glob pattern to a variable name
|
137
|
+
- `-R, --recursive`: Enable recursive directory/pattern scanning
|
138
|
+
|
139
|
+
- Variables:
|
140
|
+
- `-V name=value`: Define a simple string variable
|
141
|
+
- `-J name='{"key":"value"}'`: Define a JSON variable
|
142
|
+
|
143
|
+
- Model Parameters:
|
144
|
+
- `-m, --model MODEL`: Select the OpenAI model (supported: gpt-4o, o1, o3-mini)
|
145
|
+
- `--temperature FLOAT`: Set sampling temperature (0.0-2.0)
|
146
|
+
- `--max-output-tokens INT`: Set maximum output tokens
|
147
|
+
- `--top-p FLOAT`: Set top-p sampling parameter (0.0-1.0)
|
148
|
+
- `--frequency-penalty FLOAT`: Adjust frequency penalty (-2.0-2.0)
|
149
|
+
- `--presence-penalty FLOAT`: Adjust presence penalty (-2.0-2.0)
|
150
|
+
- `--reasoning-effort [low|medium|high]`: Control model reasoning effort
|
151
|
+
|
152
|
+
- System Prompt:
|
153
|
+
- `--sys-prompt TEXT`: Provide system prompt directly
|
154
|
+
- `--sys-file FILE`: Load system prompt from file
|
155
|
+
- `--ignore-task-sysprompt`: Ignore system prompt in template frontmatter
|
156
|
+
|
157
|
+
- API Configuration:
|
158
|
+
- `--api-key KEY`: OpenAI API key (defaults to OPENAI_API_KEY env var)
|
159
|
+
- `--timeout FLOAT`: API timeout in seconds (default: 60.0)
|
116
160
|
|
117
161
|
## Debug Options
|
118
162
|
|
119
|
-
- `--show-model-schema`: Display the generated Pydantic model schema
|
120
163
|
- `--debug-validation`: Show detailed schema validation debugging
|
121
|
-
- `--
|
122
|
-
- `--
|
123
|
-
-
|
164
|
+
- `--debug-openai-stream`: Enable low-level debug output for OpenAI streaming
|
165
|
+
- `--progress-level {none,basic,detailed}`: Set progress reporting level
|
166
|
+
- `none`: No progress indicators
|
167
|
+
- `basic`: Show key operation steps (default)
|
168
|
+
- `detailed`: Show all steps with additional info
|
169
|
+
- `--show-model-schema`: Display the generated Pydantic model schema
|
170
|
+
- `--verbose`: Enable verbose logging
|
171
|
+
- `--dry-run`: Validate and render template without making API calls
|
172
|
+
- `--no-progress`: Disable all progress indicators
|
124
173
|
|
125
174
|
All debug and error logs are written to:
|
126
175
|
|
@@ -174,13 +223,3 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
174
223
|
|
175
224
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
176
225
|
|
177
|
-
## Migration from openai-structured
|
178
|
-
|
179
|
-
If you were previously using the CLI bundled with openai-structured (pre-1.0.0), this is its new home. The migration is straightforward:
|
180
|
-
|
181
|
-
1. Update openai-structured to version 1.0.0 or later
|
182
|
-
2. Install ostruct-cli
|
183
|
-
3. Replace any `openai-structured` CLI commands with `ostruct`
|
184
|
-
|
185
|
-
The functionality remains the same, just moved to a dedicated package for better maintenance and focus.
|
186
|
-
|
@@ -63,7 +63,16 @@ Extract information about the person: {{ stdin }}
|
|
63
63
|
4. Run the CLI:
|
64
64
|
|
65
65
|
```bash
|
66
|
-
|
66
|
+
ostruct run task.j2 schema.json
|
67
|
+
```
|
68
|
+
|
69
|
+
Or with more options:
|
70
|
+
|
71
|
+
```bash
|
72
|
+
ostruct run task.j2 schema.json \
|
73
|
+
-f content input.txt \
|
74
|
+
-m gpt-4o \
|
75
|
+
--sys-prompt "You are an expert content analyzer"
|
67
76
|
```
|
68
77
|
|
69
78
|
Output:
|
@@ -84,15 +93,56 @@ Template files use the `.j2` extension to indicate they contain Jinja2 template
|
|
84
93
|
- Makes it clear the file contains template logic
|
85
94
|
- Follows industry standards for Jinja2 templates
|
86
95
|
|
87
|
-
|
96
|
+
## CLI Options
|
97
|
+
|
98
|
+
The CLI revolves around a single subcommand called `run`. Basic usage:
|
99
|
+
|
100
|
+
```bash
|
101
|
+
ostruct run <TASK_TEMPLATE> <SCHEMA_FILE> [OPTIONS]
|
102
|
+
```
|
103
|
+
|
104
|
+
Common options include:
|
105
|
+
|
106
|
+
- File & Directory Inputs:
|
107
|
+
- `-f <NAME> <PATH>`: Map a single file to a variable name
|
108
|
+
- `-d <NAME> <DIR>`: Map a directory to a variable name
|
109
|
+
- `-p <NAME> <PATTERN>`: Map files matching a glob pattern to a variable name
|
110
|
+
- `-R, --recursive`: Enable recursive directory/pattern scanning
|
111
|
+
|
112
|
+
- Variables:
|
113
|
+
- `-V name=value`: Define a simple string variable
|
114
|
+
- `-J name='{"key":"value"}'`: Define a JSON variable
|
115
|
+
|
116
|
+
- Model Parameters:
|
117
|
+
- `-m, --model MODEL`: Select the OpenAI model (supported: gpt-4o, o1, o3-mini)
|
118
|
+
- `--temperature FLOAT`: Set sampling temperature (0.0-2.0)
|
119
|
+
- `--max-output-tokens INT`: Set maximum output tokens
|
120
|
+
- `--top-p FLOAT`: Set top-p sampling parameter (0.0-1.0)
|
121
|
+
- `--frequency-penalty FLOAT`: Adjust frequency penalty (-2.0-2.0)
|
122
|
+
- `--presence-penalty FLOAT`: Adjust presence penalty (-2.0-2.0)
|
123
|
+
- `--reasoning-effort [low|medium|high]`: Control model reasoning effort
|
124
|
+
|
125
|
+
- System Prompt:
|
126
|
+
- `--sys-prompt TEXT`: Provide system prompt directly
|
127
|
+
- `--sys-file FILE`: Load system prompt from file
|
128
|
+
- `--ignore-task-sysprompt`: Ignore system prompt in template frontmatter
|
129
|
+
|
130
|
+
- API Configuration:
|
131
|
+
- `--api-key KEY`: OpenAI API key (defaults to OPENAI_API_KEY env var)
|
132
|
+
- `--timeout FLOAT`: API timeout in seconds (default: 60.0)
|
88
133
|
|
89
134
|
## Debug Options
|
90
135
|
|
91
|
-
- `--show-model-schema`: Display the generated Pydantic model schema
|
92
136
|
- `--debug-validation`: Show detailed schema validation debugging
|
93
|
-
- `--
|
94
|
-
- `--
|
95
|
-
-
|
137
|
+
- `--debug-openai-stream`: Enable low-level debug output for OpenAI streaming
|
138
|
+
- `--progress-level {none,basic,detailed}`: Set progress reporting level
|
139
|
+
- `none`: No progress indicators
|
140
|
+
- `basic`: Show key operation steps (default)
|
141
|
+
- `detailed`: Show all steps with additional info
|
142
|
+
- `--show-model-schema`: Display the generated Pydantic model schema
|
143
|
+
- `--verbose`: Enable verbose logging
|
144
|
+
- `--dry-run`: Validate and render template without making API calls
|
145
|
+
- `--no-progress`: Disable all progress indicators
|
96
146
|
|
97
147
|
All debug and error logs are written to:
|
98
148
|
|
@@ -145,13 +195,3 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
145
195
|
## License
|
146
196
|
|
147
197
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
148
|
-
|
149
|
-
## Migration from openai-structured
|
150
|
-
|
151
|
-
If you were previously using the CLI bundled with openai-structured (pre-1.0.0), this is its new home. The migration is straightforward:
|
152
|
-
|
153
|
-
1. Update openai-structured to version 1.0.0 or later
|
154
|
-
2. Install ostruct-cli
|
155
|
-
3. Replace any `openai-structured` CLI commands with `ostruct`
|
156
|
-
|
157
|
-
The functionality remains the same, just moved to a dedicated package for better maintenance and focus.
|
@@ -4,27 +4,28 @@
|
|
4
4
|
|
5
5
|
[tool.poetry]
|
6
6
|
name = "ostruct-cli"
|
7
|
-
version = "0.
|
7
|
+
version = "0.5.0"
|
8
8
|
description = "CLI for OpenAI Structured Output"
|
9
9
|
authors = ["Yaniv Golan <yaniv@golan.name>"]
|
10
10
|
readme = "README.md"
|
11
11
|
packages = [{include = "ostruct", from = "src"}]
|
12
|
+
include = ["py.typed"]
|
12
13
|
|
13
14
|
[tool.poetry.dependencies]
|
14
|
-
python = ">=3.
|
15
|
+
python = ">=3.10,<4.0"
|
15
16
|
pydantic = "^2.6.3"
|
16
17
|
jsonschema = "^4.23.0"
|
17
18
|
chardet = "^5.0.0"
|
18
19
|
cachetools = "^5.3.2"
|
19
20
|
ijson = "^3.2.3"
|
20
21
|
typing-extensions = "^4.9.0"
|
21
|
-
tiktoken = "^0.
|
22
|
+
tiktoken = "^0.9.0"
|
22
23
|
pyyaml = "^6.0.2"
|
23
24
|
tomli = {version = "^2.0.1", python = "<3.11"}
|
24
25
|
click = "^8.1.7"
|
25
26
|
werkzeug = "^3.1.3"
|
26
27
|
openai = "^1.0.0"
|
27
|
-
openai-structured = "^
|
28
|
+
openai-structured = "^2.0.0"
|
28
29
|
|
29
30
|
[tool.poetry.scripts]
|
30
31
|
ostruct = "ostruct.cli.cli:main"
|
@@ -68,26 +69,31 @@
|
|
68
69
|
|
69
70
|
[tool.mypy]
|
70
71
|
plugins = ["pydantic.mypy"]
|
71
|
-
|
72
|
-
exclude = [
|
73
|
-
"docs/",
|
74
|
-
"examples/"
|
75
|
-
]
|
76
|
-
packages = ["ostruct", "tests"]
|
77
|
-
python_version = "3.9"
|
72
|
+
python_version = "3.10"
|
78
73
|
warn_unused_configs = true
|
79
|
-
|
74
|
+
exclude = ["docs/*", "examples/*"]
|
75
|
+
disallow_untyped_defs = false
|
80
76
|
check_untyped_defs = true
|
81
|
-
|
82
|
-
warn_unused_ignores =
|
83
|
-
warn_return_any = true
|
84
|
-
warn_unreachable = true
|
77
|
+
warn_return_any = false
|
78
|
+
warn_unused_ignores = false
|
85
79
|
show_error_codes = true
|
86
|
-
|
80
|
+
|
81
|
+
# Stricter settings for source code
|
82
|
+
[[tool.mypy.overrides]]
|
83
|
+
module = "ostruct.*"
|
84
|
+
disallow_untyped_defs = true
|
85
|
+
warn_return_any = true
|
86
|
+
warn_unused_ignores = true
|
87
|
+
|
88
|
+
# Special handling for Click-related code
|
89
|
+
[[tool.mypy.overrides]]
|
90
|
+
module = ["click.*", "ostruct.cli.click_options"]
|
91
|
+
disallow_untyped_decorators = false
|
92
|
+
warn_return_any = false
|
87
93
|
|
88
94
|
[tool.black]
|
89
95
|
line-length = 79
|
90
|
-
target-version = ["
|
96
|
+
target-version = ["py310"]
|
91
97
|
include = '\.pyi?$'
|
92
98
|
preview = false
|
93
99
|
required-version = "24.8.0"
|
@@ -109,7 +115,7 @@
|
|
109
115
|
asyncio_default_fixture_loop_scope = "function"
|
110
116
|
|
111
117
|
[tool.ruff]
|
112
|
-
target-version = "
|
118
|
+
target-version = "py310"
|
113
119
|
|
114
120
|
[tool.poetry.group.examples]
|
115
121
|
optional = true
|
@@ -0,0 +1,183 @@
|
|
1
|
+
"""Base error classes for CLI error handling."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import socket
|
5
|
+
import sys
|
6
|
+
from datetime import datetime
|
7
|
+
from typing import Any, Dict, Optional
|
8
|
+
|
9
|
+
import click
|
10
|
+
|
11
|
+
from .. import __version__
|
12
|
+
from .exit_codes import ExitCode
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
class CLIError(Exception):
|
18
|
+
"""Base class for CLI errors.
|
19
|
+
|
20
|
+
Context Dictionary Conventions:
|
21
|
+
- schema_path: Path to schema file (preserved for compatibility)
|
22
|
+
- source: Origin of error (new standard, falls back to schema_path)
|
23
|
+
- path: File or directory path
|
24
|
+
- details: Additional structured error information
|
25
|
+
- timestamp: When the error occurred
|
26
|
+
- host: System hostname
|
27
|
+
- version: Software version
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
message: str,
|
33
|
+
context: Optional[Dict[str, Any]] = None,
|
34
|
+
exit_code: int = ExitCode.INTERNAL_ERROR,
|
35
|
+
details: Optional[str] = None,
|
36
|
+
) -> None:
|
37
|
+
"""Initialize CLI error.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
message: Error message
|
41
|
+
context: Optional context dictionary following conventions
|
42
|
+
exit_code: Exit code to use (defaults to INTERNAL_ERROR)
|
43
|
+
details: Optional detailed explanation of the error
|
44
|
+
"""
|
45
|
+
super().__init__(message)
|
46
|
+
self.message = message
|
47
|
+
self.context = context or {}
|
48
|
+
self.exit_code = exit_code
|
49
|
+
|
50
|
+
# Add standard context fields
|
51
|
+
if details:
|
52
|
+
self.context["details"] = details
|
53
|
+
|
54
|
+
# Add runtime context
|
55
|
+
self.context.update(
|
56
|
+
{
|
57
|
+
"timestamp": datetime.utcnow().isoformat(),
|
58
|
+
"host": socket.gethostname(),
|
59
|
+
"version": __version__,
|
60
|
+
"python_version": sys.version.split()[0],
|
61
|
+
}
|
62
|
+
)
|
63
|
+
|
64
|
+
@property
|
65
|
+
def source(self) -> Optional[str]:
|
66
|
+
"""Get error source with backward compatibility."""
|
67
|
+
return self.context.get("source") or self.context.get("schema_path")
|
68
|
+
|
69
|
+
def show(self, file: bool = True) -> None:
|
70
|
+
"""Display error message to user.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
file: Whether to write to stderr (True) or stdout (False)
|
74
|
+
"""
|
75
|
+
click.secho(str(self), fg="red", err=file)
|
76
|
+
|
77
|
+
def __str__(self) -> str:
|
78
|
+
"""Get string representation of error.
|
79
|
+
|
80
|
+
Format:
|
81
|
+
[ERROR_TYPE] Primary Message
|
82
|
+
Details: Explanation
|
83
|
+
Source: Origin
|
84
|
+
Path: /path/to/resource
|
85
|
+
Additional context fields...
|
86
|
+
Troubleshooting:
|
87
|
+
1. First step
|
88
|
+
2. Second step
|
89
|
+
"""
|
90
|
+
# Get error type from exit code
|
91
|
+
error_type = ExitCode(self.exit_code).name
|
92
|
+
|
93
|
+
# Start with error type and message
|
94
|
+
lines = [f"[{error_type}] {self.message}"]
|
95
|
+
|
96
|
+
# Add details if present
|
97
|
+
if details := self.context.get("details"):
|
98
|
+
lines.append(f"Details: {details}")
|
99
|
+
|
100
|
+
# Add source and path information
|
101
|
+
if source := self.source:
|
102
|
+
lines.append(f"Source: {source}")
|
103
|
+
if path := self.context.get("path"):
|
104
|
+
lines.append(f"Path: {path}")
|
105
|
+
|
106
|
+
# Add any expanded path information
|
107
|
+
if original_path := self.context.get("original_path"):
|
108
|
+
lines.append(f"Original Path: {original_path}")
|
109
|
+
if expanded_path := self.context.get("expanded_path"):
|
110
|
+
lines.append(f"Expanded Path: {expanded_path}")
|
111
|
+
if base_dir := self.context.get("base_dir"):
|
112
|
+
lines.append(f"Base Directory: {base_dir}")
|
113
|
+
if allowed_dirs := self.context.get("allowed_dirs"):
|
114
|
+
if isinstance(allowed_dirs, list):
|
115
|
+
lines.append(f"Allowed Directories: {allowed_dirs}")
|
116
|
+
else:
|
117
|
+
lines.append(f"Allowed Directory: {allowed_dirs}")
|
118
|
+
|
119
|
+
# Add other context fields (excluding reserved ones)
|
120
|
+
reserved_keys = {
|
121
|
+
"source",
|
122
|
+
"details",
|
123
|
+
"schema_path",
|
124
|
+
"timestamp",
|
125
|
+
"host",
|
126
|
+
"version",
|
127
|
+
"python_version",
|
128
|
+
"troubleshooting",
|
129
|
+
"path",
|
130
|
+
"original_path",
|
131
|
+
"expanded_path",
|
132
|
+
"base_dir",
|
133
|
+
"allowed_dirs",
|
134
|
+
}
|
135
|
+
context_lines = []
|
136
|
+
for k, v in sorted(self.context.items()):
|
137
|
+
if k not in reserved_keys and v is not None:
|
138
|
+
# Convert key to title case and replace underscores with spaces
|
139
|
+
formatted_key = k.replace("_", " ").title()
|
140
|
+
context_lines.append(f"{formatted_key}: {v}")
|
141
|
+
|
142
|
+
if context_lines:
|
143
|
+
lines.extend(["", "Additional Information:"])
|
144
|
+
lines.extend(context_lines)
|
145
|
+
|
146
|
+
# Add troubleshooting tips if available
|
147
|
+
if tips := self.context.get("troubleshooting"):
|
148
|
+
lines.extend(["", "Troubleshooting:"])
|
149
|
+
if isinstance(tips, list):
|
150
|
+
lines.extend(f" {i+1}. {tip}" for i, tip in enumerate(tips))
|
151
|
+
else:
|
152
|
+
lines.append(f" 1. {tips}")
|
153
|
+
|
154
|
+
return "\n".join(lines)
|
155
|
+
|
156
|
+
|
157
|
+
class OstructFileNotFoundError(CLIError):
|
158
|
+
"""Raised when a file is not found.
|
159
|
+
|
160
|
+
This is Ostruct's custom error for file not found scenarios, distinct from Python's built-in
|
161
|
+
FileNotFoundError. It provides additional context and troubleshooting information specific to
|
162
|
+
the CLI context.
|
163
|
+
"""
|
164
|
+
|
165
|
+
def __init__(self, path: str, context: Optional[Dict[str, Any]] = None):
|
166
|
+
context = context or {}
|
167
|
+
context.update(
|
168
|
+
{
|
169
|
+
"details": "The specified file does not exist or cannot be accessed",
|
170
|
+
"path": path,
|
171
|
+
"troubleshooting": [
|
172
|
+
"Check if the file exists",
|
173
|
+
"Verify the path spelling is correct",
|
174
|
+
"Check file permissions",
|
175
|
+
"Ensure parent directories exist",
|
176
|
+
],
|
177
|
+
}
|
178
|
+
)
|
179
|
+
super().__init__(
|
180
|
+
f"File not found: {path}",
|
181
|
+
exit_code=ExitCode.FILE_ERROR,
|
182
|
+
context=context,
|
183
|
+
)
|