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.
Files changed (44) hide show
  1. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/PKG-INFO +107 -4
  2. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/README.md +104 -2
  3. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/pyproject.toml +3 -2
  4. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/__init__.py +2 -0
  5. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/cli.py +85 -4
  6. ostruct_cli-0.7.1/src/ostruct/cli/registry_updates.py +162 -0
  7. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/LICENSE +0 -0
  8. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/__init__.py +0 -0
  9. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/base_errors.py +0 -0
  10. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/cache_manager.py +0 -0
  11. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/click_options.py +0 -0
  12. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/errors.py +0 -0
  13. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/exit_codes.py +0 -0
  14. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/file_info.py +0 -0
  15. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/file_list.py +0 -0
  16. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/file_utils.py +0 -0
  17. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/model_creation.py +0 -0
  18. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/path_utils.py +0 -0
  19. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/progress.py +0 -0
  20. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/schema_validation.py +0 -0
  21. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/__init__.py +0 -0
  22. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/allowed_checker.py +0 -0
  23. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/base.py +0 -0
  24. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/case_manager.py +0 -0
  25. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/errors.py +0 -0
  26. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/normalization.py +0 -0
  27. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/safe_joiner.py +0 -0
  28. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/security_manager.py +0 -0
  29. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/symlink_resolver.py +0 -0
  30. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/types.py +0 -0
  31. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/security/windows_paths.py +0 -0
  32. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/serialization.py +0 -0
  33. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_env.py +0 -0
  34. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_extensions.py +0 -0
  35. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_filters.py +0 -0
  36. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_io.py +0 -0
  37. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_rendering.py +0 -0
  38. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_schema.py +0 -0
  39. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_utils.py +0 -0
  40. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/template_validation.py +0 -0
  41. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/token_utils.py +0 -0
  42. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/utils.py +0 -0
  43. {ostruct_cli-0.6.2 → ostruct_cli-0.7.1}/src/ostruct/cli/validators.py +0 -0
  44. {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.6.2
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 (>=2.0.0,<3.0.0)
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
- # ostruct-cli
29
+ ![ostruct](src/assets/ostruct-header.png)
30
+
31
+ <div align="center">
29
32
 
30
33
  [![PyPI version](https://badge.fury.io/py/ostruct-cli.svg)](https://badge.fury.io/py/ostruct-cli)
31
34
  [![Python Versions](https://img.shields.io/pypi/pyversions/ostruct-cli.svg)](https://pypi.org/project/ostruct-cli)
@@ -33,10 +36,66 @@ Description-Content-Type: text/markdown
33
36
  [![CI](https://github.com/yaniv-golan/ostruct/actions/workflows/ci.yml/badge.svg)](https://github.com/yaniv-golan/ostruct/actions/workflows/ci.yml)
34
37
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
35
38
 
36
- ostruct tranforms unstructured inputs into structured, usable JSON output using OpenAI APIs.
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
+ ![How ostruct works](src/assets/ostrict-hl-diagram.png)
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
- # ostruct-cli
1
+ ![ostruct](src/assets/ostruct-header.png)
2
+
3
+ <div align="center">
2
4
 
3
5
  [![PyPI version](https://badge.fury.io/py/ostruct-cli.svg)](https://badge.fury.io/py/ostruct-cli)
4
6
  [![Python Versions](https://img.shields.io/pypi/pyversions/ostruct-cli.svg)](https://pypi.org/project/ostruct-cli)
@@ -6,10 +8,66 @@
6
8
  [![CI](https://github.com/yaniv-golan/ostruct/actions/workflows/ci.yml/badge.svg)](https://github.com/yaniv-golan/ostruct/actions/workflows/ci.yml)
7
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
10
 
9
- ostruct tranforms unstructured inputs into structured, usable JSON output using OpenAI APIs.
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
+ ![How ostruct works](src/assets/ostrict-hl-diagram.png)
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.6.2"
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 = "^2.0.0"
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 ModelRegistry
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
- pass
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
- # Remove the old @create_click_command() decorator and cli function definition
1564
- # Keep all the other functions and code below this point
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