ostruct-cli 0.6.2__tar.gz → 0.7.1__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.6.2 → ostruct_cli-0.7.1}/PKG-INFO +107 -4
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/README.md +104 -2
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/pyproject.toml +3 -2
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/__init__.py +2 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/cli.py +85 -4
- ostruct_cli-0.7.1/src/ostruct/cli/registry_updates.py +162 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/LICENSE +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/__init__.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/base_errors.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/cache_manager.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/click_options.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/errors.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/exit_codes.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/file_info.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/file_list.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/file_utils.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/model_creation.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/path_utils.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/progress.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/schema_validation.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/__init__.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/allowed_checker.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/base.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/case_manager.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/errors.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/normalization.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/safe_joiner.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/security_manager.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/symlink_resolver.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/types.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/windows_paths.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/serialization.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_env.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_extensions.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_filters.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_io.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_rendering.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_schema.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_utils.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_validation.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/token_utils.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/utils.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/validators.py +0 -0
- {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/py.typed +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ostruct-cli
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.1
|
4
4
|
Summary: CLI for OpenAI Structured Output
|
5
5
|
Author: Yaniv Golan
|
6
6
|
Author-email: yaniv@golan.name
|
@@ -16,8 +16,9 @@ Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
16
16
|
Requires-Dist: ijson (>=3.2.3,<4.0.0)
|
17
17
|
Requires-Dist: jsonschema (>=4.23.0,<5.0.0)
|
18
18
|
Requires-Dist: openai (>=1.0.0,<2.0.0)
|
19
|
-
Requires-Dist: openai-structured (>=
|
19
|
+
Requires-Dist: openai-structured (>=3.0.0,<4.0.0)
|
20
20
|
Requires-Dist: pydantic (>=2.6.3,<3.0.0)
|
21
|
+
Requires-Dist: pygments (>=2.15.0,<3.0.0)
|
21
22
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
22
23
|
Requires-Dist: tiktoken (==0.9.0)
|
23
24
|
Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
|
@@ -25,7 +26,9 @@ Requires-Dist: typing-extensions (>=4.9.0,<5.0.0)
|
|
25
26
|
Requires-Dist: werkzeug (>=3.1.3,<4.0.0)
|
26
27
|
Description-Content-Type: text/markdown
|
27
28
|
|
28
|
-
|
29
|
+

|
30
|
+
|
31
|
+
<div align="center">
|
29
32
|
|
30
33
|
[](https://badge.fury.io/py/ostruct-cli)
|
31
34
|
[](https://pypi.org/project/ostruct-cli)
|
@@ -33,10 +36,66 @@ Description-Content-Type: text/markdown
|
|
33
36
|
[](https://github.com/yaniv-golan/ostruct/actions/workflows/ci.yml)
|
34
37
|
[](https://opensource.org/licenses/MIT)
|
35
38
|
|
36
|
-
ostruct tranforms unstructured inputs into structured
|
39
|
+
**ostruct** tranforms **unstructured** inputs into **structured**, usable **JSON** output using **OpenAI APIs** using dynamic **templates**
|
40
|
+
|
41
|
+
</div>
|
42
|
+
|
43
|
+
# ostruct-cli
|
37
44
|
|
38
45
|
ostruct will process a set of plain text files (data, source code, CSV, etc), input variables, a dynamic prompt template, and a JSON schema specifying the desired output format, and will produce the result in JSON format.
|
39
46
|
|
47
|
+
<div align="center">
|
48
|
+
|
49
|
+

|
50
|
+
|
51
|
+
</div>
|
52
|
+
|
53
|
+
## Why ostruct?
|
54
|
+
|
55
|
+
LLMs are powerful, but getting consistent, structured output from them can be challenging. ostruct solves this problem by providing a streamlined approach to transform unstructured data into reliable JSON structures. The motivation behind creating ostruct was to:
|
56
|
+
|
57
|
+
- **Bridge the gap** between freeform LLM capabilities and structured data needs in production systems
|
58
|
+
- **Simplify integration** of AI into existing workflows and applications that expect consistent data formats
|
59
|
+
- **Ensure reliability** and validate output against a defined schema to avoid unexpected formats or missing data
|
60
|
+
- **Reduce development time** by providing a standardized way to interact with OpenAI models for structured outputs
|
61
|
+
- **Enable non-developers** to leverage AI capabilities through a simple CLI interface with templates
|
62
|
+
|
63
|
+
## Real-World Use Cases
|
64
|
+
|
65
|
+
ostruct can be used for various scenarios, including:
|
66
|
+
|
67
|
+
### Etymology Analysis
|
68
|
+
|
69
|
+
```bash
|
70
|
+
ostruct run prompts/task.j2 schemas/etymology.json -f input examples/scientific.txt --model gpt-4o
|
71
|
+
```
|
72
|
+
|
73
|
+
Break down words into their components, showing their origins, meanings, and hierarchical relationships. Useful for linguistics, educational tools, and understanding terminology in specialized fields.
|
74
|
+
|
75
|
+
### Automated Code Review
|
76
|
+
|
77
|
+
```bash
|
78
|
+
ostruct run prompts/task.j2 schemas/code_review.json -p source "examples/security/*.py" --model gpt-4o
|
79
|
+
```
|
80
|
+
|
81
|
+
Analyze code for security vulnerabilities, style issues, and performance problems, producing structured reports that can be easily integrated into CI/CD pipelines or developer workflows.
|
82
|
+
|
83
|
+
### Security Vulnerability Scanning
|
84
|
+
|
85
|
+
```bash
|
86
|
+
ostruct run prompts/task.j2 schemas/scan_result.json -d examples/intermediate --model gpt-4o
|
87
|
+
```
|
88
|
+
|
89
|
+
Scan codebases for security vulnerabilities, combining static analysis with AI-powered reasoning to identify potential issues, suggest fixes, and provide detailed explanations.
|
90
|
+
|
91
|
+
### Configuration Validation & Analysis
|
92
|
+
|
93
|
+
```bash
|
94
|
+
ostruct run prompts/task.j2 schemas/validation_result.json -f dev examples/basic/dev.yaml -f prod examples/basic/prod.yaml
|
95
|
+
```
|
96
|
+
|
97
|
+
Validate configuration files across environments, check for inconsistencies, and provide intelligent feedback on potential issues or improvements in infrastructure setups.
|
98
|
+
|
40
99
|
## Features
|
41
100
|
|
42
101
|
- Generate structured JSON output from natural language using OpenAI models and a JSON schema
|
@@ -44,6 +103,8 @@ ostruct will process a set of plain text files (data, source code, CSV, etc), in
|
|
44
103
|
- Automatic token counting and context window management
|
45
104
|
- Streaming support for real-time output
|
46
105
|
- Secure handling of sensitive data
|
106
|
+
- Model registry management with support for updating to the latest OpenAI models
|
107
|
+
- Non-intrusive registry update checks with user notifications
|
47
108
|
|
48
109
|
## Requirements
|
49
110
|
|
@@ -63,6 +124,16 @@ pip install ostruct-cli
|
|
63
124
|
|
64
125
|
If you plan to contribute to the project, see the [Development Setup](#development-setup) section below for instructions on setting up the development environment with Poetry.
|
65
126
|
|
127
|
+
## Environment Variables
|
128
|
+
|
129
|
+
ostruct-cli respects the following environment variables:
|
130
|
+
|
131
|
+
- `OPENAI_API_KEY`: Your OpenAI API key (required unless provided via command line)
|
132
|
+
- `OPENAI_API_BASE`: Custom API base URL (optional)
|
133
|
+
- `OPENAI_API_VERSION`: API version to use (optional)
|
134
|
+
- `OPENAI_API_TYPE`: API type (e.g., "azure") (optional)
|
135
|
+
- `OSTRUCT_DISABLE_UPDATE_CHECKS`: Set to "1", "true", or "yes" to disable automatic registry update checks
|
136
|
+
|
66
137
|
## Shell Completion
|
67
138
|
|
68
139
|
ostruct-cli supports shell completion for Bash, Zsh, and Fish shells. To enable it:
|
@@ -264,3 +335,35 @@ ostruct run template.j2 schema.json --sys-prompt "Override prompt"
|
|
264
335
|
ostruct run template.j2 schema.json --ignore-task-sysprompt
|
265
336
|
```
|
266
337
|
|
338
|
+
## Model Registry Management
|
339
|
+
|
340
|
+
ostruct-cli maintains a registry of OpenAI models and their capabilities, which includes:
|
341
|
+
|
342
|
+
- Context window sizes for each model
|
343
|
+
- Maximum output token limits
|
344
|
+
- Supported parameters and their constraints
|
345
|
+
- Model version information
|
346
|
+
|
347
|
+
To ensure you're using the latest models and features, you can update the registry:
|
348
|
+
|
349
|
+
```bash
|
350
|
+
# Update from the official repository
|
351
|
+
ostruct update-registry
|
352
|
+
|
353
|
+
# Update from a custom URL
|
354
|
+
ostruct update-registry --url https://example.com/models.yml
|
355
|
+
|
356
|
+
# Force an update even if the registry is current
|
357
|
+
ostruct update-registry --force
|
358
|
+
```
|
359
|
+
|
360
|
+
This is especially useful when:
|
361
|
+
|
362
|
+
- New OpenAI models are released
|
363
|
+
- Model capabilities or parameters change
|
364
|
+
- You need to work with custom model configurations
|
365
|
+
|
366
|
+
The registry file is stored at `~/.openai_structured/config/models.yml` and is automatically referenced when validating model parameters and token limits.
|
367
|
+
|
368
|
+
The update command uses HTTP conditional requests (If-Modified-Since headers) to check if the remote registry has changed before downloading, ensuring efficient updates.
|
369
|
+
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+

|
2
|
+
|
3
|
+
<div align="center">
|
2
4
|
|
3
5
|
[](https://badge.fury.io/py/ostruct-cli)
|
4
6
|
[](https://pypi.org/project/ostruct-cli)
|
@@ -6,10 +8,66 @@
|
|
6
8
|
[](https://github.com/yaniv-golan/ostruct/actions/workflows/ci.yml)
|
7
9
|
[](https://opensource.org/licenses/MIT)
|
8
10
|
|
9
|
-
ostruct tranforms unstructured inputs into structured
|
11
|
+
**ostruct** tranforms **unstructured** inputs into **structured**, usable **JSON** output using **OpenAI APIs** using dynamic **templates**
|
12
|
+
|
13
|
+
</div>
|
14
|
+
|
15
|
+
# ostruct-cli
|
10
16
|
|
11
17
|
ostruct will process a set of plain text files (data, source code, CSV, etc), input variables, a dynamic prompt template, and a JSON schema specifying the desired output format, and will produce the result in JSON format.
|
12
18
|
|
19
|
+
<div align="center">
|
20
|
+
|
21
|
+

|
22
|
+
|
23
|
+
</div>
|
24
|
+
|
25
|
+
## Why ostruct?
|
26
|
+
|
27
|
+
LLMs are powerful, but getting consistent, structured output from them can be challenging. ostruct solves this problem by providing a streamlined approach to transform unstructured data into reliable JSON structures. The motivation behind creating ostruct was to:
|
28
|
+
|
29
|
+
- **Bridge the gap** between freeform LLM capabilities and structured data needs in production systems
|
30
|
+
- **Simplify integration** of AI into existing workflows and applications that expect consistent data formats
|
31
|
+
- **Ensure reliability** and validate output against a defined schema to avoid unexpected formats or missing data
|
32
|
+
- **Reduce development time** by providing a standardized way to interact with OpenAI models for structured outputs
|
33
|
+
- **Enable non-developers** to leverage AI capabilities through a simple CLI interface with templates
|
34
|
+
|
35
|
+
## Real-World Use Cases
|
36
|
+
|
37
|
+
ostruct can be used for various scenarios, including:
|
38
|
+
|
39
|
+
### Etymology Analysis
|
40
|
+
|
41
|
+
```bash
|
42
|
+
ostruct run prompts/task.j2 schemas/etymology.json -f input examples/scientific.txt --model gpt-4o
|
43
|
+
```
|
44
|
+
|
45
|
+
Break down words into their components, showing their origins, meanings, and hierarchical relationships. Useful for linguistics, educational tools, and understanding terminology in specialized fields.
|
46
|
+
|
47
|
+
### Automated Code Review
|
48
|
+
|
49
|
+
```bash
|
50
|
+
ostruct run prompts/task.j2 schemas/code_review.json -p source "examples/security/*.py" --model gpt-4o
|
51
|
+
```
|
52
|
+
|
53
|
+
Analyze code for security vulnerabilities, style issues, and performance problems, producing structured reports that can be easily integrated into CI/CD pipelines or developer workflows.
|
54
|
+
|
55
|
+
### Security Vulnerability Scanning
|
56
|
+
|
57
|
+
```bash
|
58
|
+
ostruct run prompts/task.j2 schemas/scan_result.json -d examples/intermediate --model gpt-4o
|
59
|
+
```
|
60
|
+
|
61
|
+
Scan codebases for security vulnerabilities, combining static analysis with AI-powered reasoning to identify potential issues, suggest fixes, and provide detailed explanations.
|
62
|
+
|
63
|
+
### Configuration Validation & Analysis
|
64
|
+
|
65
|
+
```bash
|
66
|
+
ostruct run prompts/task.j2 schemas/validation_result.json -f dev examples/basic/dev.yaml -f prod examples/basic/prod.yaml
|
67
|
+
```
|
68
|
+
|
69
|
+
Validate configuration files across environments, check for inconsistencies, and provide intelligent feedback on potential issues or improvements in infrastructure setups.
|
70
|
+
|
13
71
|
## Features
|
14
72
|
|
15
73
|
- Generate structured JSON output from natural language using OpenAI models and a JSON schema
|
@@ -17,6 +75,8 @@ ostruct will process a set of plain text files (data, source code, CSV, etc), in
|
|
17
75
|
- Automatic token counting and context window management
|
18
76
|
- Streaming support for real-time output
|
19
77
|
- Secure handling of sensitive data
|
78
|
+
- Model registry management with support for updating to the latest OpenAI models
|
79
|
+
- Non-intrusive registry update checks with user notifications
|
20
80
|
|
21
81
|
## Requirements
|
22
82
|
|
@@ -36,6 +96,16 @@ pip install ostruct-cli
|
|
36
96
|
|
37
97
|
If you plan to contribute to the project, see the [Development Setup](#development-setup) section below for instructions on setting up the development environment with Poetry.
|
38
98
|
|
99
|
+
## Environment Variables
|
100
|
+
|
101
|
+
ostruct-cli respects the following environment variables:
|
102
|
+
|
103
|
+
- `OPENAI_API_KEY`: Your OpenAI API key (required unless provided via command line)
|
104
|
+
- `OPENAI_API_BASE`: Custom API base URL (optional)
|
105
|
+
- `OPENAI_API_VERSION`: API version to use (optional)
|
106
|
+
- `OPENAI_API_TYPE`: API type (e.g., "azure") (optional)
|
107
|
+
- `OSTRUCT_DISABLE_UPDATE_CHECKS`: Set to "1", "true", or "yes" to disable automatic registry update checks
|
108
|
+
|
39
109
|
## Shell Completion
|
40
110
|
|
41
111
|
ostruct-cli supports shell completion for Bash, Zsh, and Fish shells. To enable it:
|
@@ -236,3 +306,35 @@ ostruct run template.j2 schema.json --sys-prompt "Override prompt"
|
|
236
306
|
# Ignore template frontmatter and use default
|
237
307
|
ostruct run template.j2 schema.json --ignore-task-sysprompt
|
238
308
|
```
|
309
|
+
|
310
|
+
## Model Registry Management
|
311
|
+
|
312
|
+
ostruct-cli maintains a registry of OpenAI models and their capabilities, which includes:
|
313
|
+
|
314
|
+
- Context window sizes for each model
|
315
|
+
- Maximum output token limits
|
316
|
+
- Supported parameters and their constraints
|
317
|
+
- Model version information
|
318
|
+
|
319
|
+
To ensure you're using the latest models and features, you can update the registry:
|
320
|
+
|
321
|
+
```bash
|
322
|
+
# Update from the official repository
|
323
|
+
ostruct update-registry
|
324
|
+
|
325
|
+
# Update from a custom URL
|
326
|
+
ostruct update-registry --url https://example.com/models.yml
|
327
|
+
|
328
|
+
# Force an update even if the registry is current
|
329
|
+
ostruct update-registry --force
|
330
|
+
```
|
331
|
+
|
332
|
+
This is especially useful when:
|
333
|
+
|
334
|
+
- New OpenAI models are released
|
335
|
+
- Model capabilities or parameters change
|
336
|
+
- You need to work with custom model configurations
|
337
|
+
|
338
|
+
The registry file is stored at `~/.openai_structured/config/models.yml` and is automatically referenced when validating model parameters and token limits.
|
339
|
+
|
340
|
+
The update command uses HTTP conditional requests (If-Modified-Since headers) to check if the remote registry has changed before downloading, ensuring efficient updates.
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
[tool.poetry]
|
6
6
|
name = "ostruct-cli"
|
7
|
-
version = "0.
|
7
|
+
version = "0.7.1"
|
8
8
|
description = "CLI for OpenAI Structured Output"
|
9
9
|
authors = ["Yaniv Golan <yaniv@golan.name>"]
|
10
10
|
readme = "README.md"
|
@@ -24,8 +24,9 @@
|
|
24
24
|
click = "^8.1.7"
|
25
25
|
werkzeug = "^3.1.3"
|
26
26
|
openai = "^1.0.0"
|
27
|
-
openai-structured = "^
|
27
|
+
openai-structured = "^3.0.0"
|
28
28
|
tiktoken = "0.9.0"
|
29
|
+
pygments = "^2.15.0"
|
29
30
|
|
30
31
|
[tool.poetry.scripts]
|
31
32
|
ostruct = "ostruct.cli.cli:main"
|
@@ -8,6 +8,7 @@ from .cli import (
|
|
8
8
|
validate_variable_mapping,
|
9
9
|
)
|
10
10
|
from .path_utils import validate_path_mapping
|
11
|
+
from .registry_updates import get_update_notification
|
11
12
|
|
12
13
|
__all__ = [
|
13
14
|
"ExitCode",
|
@@ -16,4 +17,5 @@ __all__ = [
|
|
16
17
|
"validate_schema_file",
|
17
18
|
"validate_task_template",
|
18
19
|
"validate_variable_mapping",
|
20
|
+
"get_update_notification",
|
19
21
|
]
|
@@ -45,7 +45,10 @@ from openai_structured.errors import (
|
|
45
45
|
OpenAIClientError,
|
46
46
|
StreamBufferError,
|
47
47
|
)
|
48
|
-
from openai_structured.model_registry import
|
48
|
+
from openai_structured.model_registry import (
|
49
|
+
ModelRegistry,
|
50
|
+
RegistryUpdateStatus,
|
51
|
+
)
|
49
52
|
from pydantic import AnyUrl, BaseModel, EmailStr, Field
|
50
53
|
from pydantic.fields import FieldInfo as FieldInfoType
|
51
54
|
from pydantic.functional_validators import BeforeValidator
|
@@ -75,6 +78,7 @@ from .errors import (
|
|
75
78
|
from .file_utils import FileInfoList, collect_files
|
76
79
|
from .model_creation import _create_enum_type, create_dynamic_model
|
77
80
|
from .path_utils import validate_path_mapping
|
81
|
+
from .registry_updates import get_update_notification
|
78
82
|
from .security import SecurityManager
|
79
83
|
from .serialization import LogSerializer
|
80
84
|
from .template_env import create_jinja_env
|
@@ -1490,7 +1494,14 @@ def cli() -> None:
|
|
1490
1494
|
|
1491
1495
|
ostruct run task.j2 schema.json -J config='{"env":"prod"}' -m o3-mini
|
1492
1496
|
"""
|
1493
|
-
|
1497
|
+
# Check for registry updates in a non-intrusive way
|
1498
|
+
try:
|
1499
|
+
update_message = get_update_notification()
|
1500
|
+
if update_message:
|
1501
|
+
click.secho(f"Note: {update_message}", fg="blue", err=True)
|
1502
|
+
except Exception:
|
1503
|
+
# Ensure any errors don't affect normal operation
|
1504
|
+
pass
|
1494
1505
|
|
1495
1506
|
|
1496
1507
|
@cli.command()
|
@@ -1560,8 +1571,78 @@ def run(
|
|
1560
1571
|
raise
|
1561
1572
|
|
1562
1573
|
|
1563
|
-
|
1564
|
-
|
1574
|
+
@cli.command("update-registry")
|
1575
|
+
@click.option(
|
1576
|
+
"--url",
|
1577
|
+
help="URL to fetch the registry from. Defaults to official repository.",
|
1578
|
+
default=None,
|
1579
|
+
)
|
1580
|
+
@click.option(
|
1581
|
+
"--force",
|
1582
|
+
is_flag=True,
|
1583
|
+
help="Force update even if the registry is already up to date.",
|
1584
|
+
default=False,
|
1585
|
+
)
|
1586
|
+
def update_registry(url: Optional[str] = None, force: bool = False) -> None:
|
1587
|
+
"""Update the model registry with the latest model definitions.
|
1588
|
+
|
1589
|
+
This command fetches the latest model registry from the official repository
|
1590
|
+
or a custom URL if provided, and updates the local registry file.
|
1591
|
+
|
1592
|
+
Example:
|
1593
|
+
ostruct update-registry
|
1594
|
+
ostruct update-registry --url https://example.com/models.yml
|
1595
|
+
"""
|
1596
|
+
try:
|
1597
|
+
registry = ModelRegistry()
|
1598
|
+
|
1599
|
+
# Show current registry config path
|
1600
|
+
config_path = registry._config_path
|
1601
|
+
click.echo(f"Current registry file: {config_path}")
|
1602
|
+
|
1603
|
+
if force:
|
1604
|
+
click.echo("Forcing registry update...")
|
1605
|
+
success = registry.refresh_from_remote(url)
|
1606
|
+
if success:
|
1607
|
+
click.echo("✅ Registry successfully updated!")
|
1608
|
+
else:
|
1609
|
+
click.echo(
|
1610
|
+
"❌ Failed to update registry. See logs for details."
|
1611
|
+
)
|
1612
|
+
sys.exit(ExitCode.SUCCESS.value)
|
1613
|
+
|
1614
|
+
if config_path is None or not os.path.exists(config_path):
|
1615
|
+
click.echo("Registry file not found. Creating new one...")
|
1616
|
+
success = registry.refresh_from_remote(url)
|
1617
|
+
if success:
|
1618
|
+
click.echo("✅ Registry successfully created!")
|
1619
|
+
else:
|
1620
|
+
click.echo(
|
1621
|
+
"❌ Failed to create registry. See logs for details."
|
1622
|
+
)
|
1623
|
+
sys.exit(ExitCode.SUCCESS.value)
|
1624
|
+
|
1625
|
+
# Use the built-in update checking functionality
|
1626
|
+
click.echo("Checking for updates...")
|
1627
|
+
update_result = registry.check_for_updates()
|
1628
|
+
|
1629
|
+
if update_result.status == RegistryUpdateStatus.UPDATE_AVAILABLE:
|
1630
|
+
click.echo(
|
1631
|
+
f"{click.style('✓', fg='green')} {update_result.message}"
|
1632
|
+
)
|
1633
|
+
exit_code = ExitCode.SUCCESS
|
1634
|
+
elif update_result.status == RegistryUpdateStatus.ALREADY_CURRENT:
|
1635
|
+
click.echo(
|
1636
|
+
f"{click.style('✓', fg='green')} Registry is up to date"
|
1637
|
+
)
|
1638
|
+
exit_code = ExitCode.SUCCESS
|
1639
|
+
else:
|
1640
|
+
click.echo("❓ Unable to determine if updates are available.")
|
1641
|
+
|
1642
|
+
sys.exit(exit_code)
|
1643
|
+
except Exception as e:
|
1644
|
+
click.echo(f"❌ Error updating registry: {str(e)}")
|
1645
|
+
sys.exit(ExitCode.API_ERROR.value)
|
1565
1646
|
|
1566
1647
|
|
1567
1648
|
async def validate_model_params(args: CLIParams) -> Dict[str, Any]:
|
@@ -0,0 +1,162 @@
|
|
1
|
+
"""Registry update checks for ostruct CLI.
|
2
|
+
|
3
|
+
This module provides functionality to check for updates to the model registry
|
4
|
+
and notify users when updates are available.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
import logging
|
9
|
+
import os
|
10
|
+
import time
|
11
|
+
from pathlib import Path
|
12
|
+
from typing import Optional, Tuple
|
13
|
+
|
14
|
+
from openai_structured.model_registry import (
|
15
|
+
ModelRegistry,
|
16
|
+
RegistryUpdateStatus,
|
17
|
+
)
|
18
|
+
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
# Constants
|
22
|
+
UPDATE_CHECK_ENV_VAR = "OSTRUCT_DISABLE_UPDATE_CHECKS"
|
23
|
+
UPDATE_CHECK_INTERVAL_SECONDS = (
|
24
|
+
86400 # Check for updates once per day (24 hours)
|
25
|
+
)
|
26
|
+
LAST_CHECK_CACHE_FILE = ".ostruct_registry_check"
|
27
|
+
|
28
|
+
|
29
|
+
def _get_cache_dir() -> Path:
|
30
|
+
"""Get the cache directory for ostruct.
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
Path: Path to the cache directory
|
34
|
+
"""
|
35
|
+
# Use XDG_CACHE_HOME if available, otherwise use ~/.cache
|
36
|
+
xdg_cache_home = os.environ.get("XDG_CACHE_HOME")
|
37
|
+
if xdg_cache_home:
|
38
|
+
base_dir = Path(xdg_cache_home)
|
39
|
+
else:
|
40
|
+
base_dir = Path.home() / ".cache"
|
41
|
+
|
42
|
+
cache_dir = base_dir / "ostruct"
|
43
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
44
|
+
return cache_dir
|
45
|
+
|
46
|
+
|
47
|
+
def _get_last_check_time() -> Optional[float]:
|
48
|
+
"""Get the timestamp of the last update check.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Optional[float]: Timestamp of the last check, or None if never checked
|
52
|
+
"""
|
53
|
+
cache_file = _get_cache_dir() / LAST_CHECK_CACHE_FILE
|
54
|
+
|
55
|
+
if not cache_file.exists():
|
56
|
+
return None
|
57
|
+
|
58
|
+
try:
|
59
|
+
with open(cache_file, "r") as f:
|
60
|
+
data = json.load(f)
|
61
|
+
last_check_time = data.get("last_check_time")
|
62
|
+
return (
|
63
|
+
float(last_check_time) if last_check_time is not None else None
|
64
|
+
)
|
65
|
+
except (json.JSONDecodeError, IOError, OSError):
|
66
|
+
return None
|
67
|
+
|
68
|
+
|
69
|
+
def _save_last_check_time() -> None:
|
70
|
+
"""Save the current time as the last update check time."""
|
71
|
+
cache_file = _get_cache_dir() / LAST_CHECK_CACHE_FILE
|
72
|
+
|
73
|
+
try:
|
74
|
+
data = {"last_check_time": time.time()}
|
75
|
+
with open(cache_file, "w") as f:
|
76
|
+
json.dump(data, f)
|
77
|
+
except (IOError, OSError) as e:
|
78
|
+
logger.debug(f"Failed to save last check time: {e}")
|
79
|
+
|
80
|
+
|
81
|
+
def should_check_for_updates() -> bool:
|
82
|
+
"""Determine if we should check for registry updates.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
bool: True if update checks are enabled, False otherwise
|
86
|
+
"""
|
87
|
+
# Allow users to disable update checks via environment variable
|
88
|
+
if os.environ.get(UPDATE_CHECK_ENV_VAR, "").lower() in (
|
89
|
+
"1",
|
90
|
+
"true",
|
91
|
+
"yes",
|
92
|
+
):
|
93
|
+
logger.debug(
|
94
|
+
"Registry update checks disabled via environment variable"
|
95
|
+
)
|
96
|
+
return False
|
97
|
+
|
98
|
+
# Check if we've checked recently
|
99
|
+
last_check_time = _get_last_check_time()
|
100
|
+
if last_check_time is not None:
|
101
|
+
time_since_last_check = time.time() - last_check_time
|
102
|
+
if time_since_last_check < UPDATE_CHECK_INTERVAL_SECONDS:
|
103
|
+
logger.debug(
|
104
|
+
f"Skipping update check, last check was {time_since_last_check:.1f} seconds ago"
|
105
|
+
)
|
106
|
+
return False
|
107
|
+
|
108
|
+
return True
|
109
|
+
|
110
|
+
|
111
|
+
def check_for_registry_updates() -> Tuple[bool, Optional[str]]:
|
112
|
+
"""Check if there are updates available for the model registry.
|
113
|
+
|
114
|
+
This function is designed to be non-intrusive and fail gracefully.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
Tuple[bool, Optional[str]]: (update_available, message)
|
118
|
+
- update_available: True if an update is available
|
119
|
+
- message: A message to display to the user, or None if no update is available
|
120
|
+
"""
|
121
|
+
if not should_check_for_updates():
|
122
|
+
return False, None
|
123
|
+
|
124
|
+
try:
|
125
|
+
registry = ModelRegistry()
|
126
|
+
result = registry.check_for_updates()
|
127
|
+
|
128
|
+
# Save the check time regardless of the result
|
129
|
+
_save_last_check_time()
|
130
|
+
|
131
|
+
if result.status == RegistryUpdateStatus.UPDATE_AVAILABLE:
|
132
|
+
return True, (
|
133
|
+
"A new model registry is available. "
|
134
|
+
"This may include support for new models or features. "
|
135
|
+
"The registry will be automatically updated when needed."
|
136
|
+
)
|
137
|
+
|
138
|
+
return False, None
|
139
|
+
except Exception as e:
|
140
|
+
# Ensure any errors don't affect normal operation
|
141
|
+
logger.debug(f"Error checking for registry updates: {e}")
|
142
|
+
return False, None
|
143
|
+
|
144
|
+
|
145
|
+
def get_update_notification() -> Optional[str]:
|
146
|
+
"""Get a notification message if registry updates are available.
|
147
|
+
|
148
|
+
This function is designed to be called from the CLI to provide
|
149
|
+
a non-intrusive notification to users.
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
Optional[str]: A notification message, or None if no notification is needed
|
153
|
+
"""
|
154
|
+
try:
|
155
|
+
update_available, message = check_for_registry_updates()
|
156
|
+
if update_available and message:
|
157
|
+
return message
|
158
|
+
return None
|
159
|
+
except Exception as e:
|
160
|
+
# Ensure any errors don't affect normal operation
|
161
|
+
logger.debug(f"Error getting update notification: {e}")
|
162
|
+
return None
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|