ostruct-cli 0.6.2__py3-none-any.whl → 0.7.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.
- ostruct/cli/__init__.py +2 -0
- ostruct/cli/cli.py +85 -4
- ostruct/cli/registry_updates.py +162 -0
- {ostruct_cli-0.6.2.dist-info → ostruct_cli-0.7.0.dist-info}/METADATA +46 -2
- {ostruct_cli-0.6.2.dist-info → ostruct_cli-0.7.0.dist-info}/RECORD +8 -7
- {ostruct_cli-0.6.2.dist-info → ostruct_cli-0.7.0.dist-info}/LICENSE +0 -0
- {ostruct_cli-0.6.2.dist-info → ostruct_cli-0.7.0.dist-info}/WHEEL +0 -0
- {ostruct_cli-0.6.2.dist-info → ostruct_cli-0.7.0.dist-info}/entry_points.txt +0 -0
ostruct/cli/__init__.py
CHANGED
@@ -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
|
]
|
ostruct/cli/cli.py
CHANGED
@@ -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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ostruct-cli
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.0
|
4
4
|
Summary: CLI for OpenAI Structured Output
|
5
5
|
Author: Yaniv Golan
|
6
6
|
Author-email: yaniv@golan.name
|
@@ -16,7 +16,7 @@ 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.
|
19
|
+
Requires-Dist: openai-structured (>=2.1.0,<3.0.0)
|
20
20
|
Requires-Dist: pydantic (>=2.6.3,<3.0.0)
|
21
21
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
22
22
|
Requires-Dist: tiktoken (==0.9.0)
|
@@ -44,6 +44,8 @@ ostruct will process a set of plain text files (data, source code, CSV, etc), in
|
|
44
44
|
- Automatic token counting and context window management
|
45
45
|
- Streaming support for real-time output
|
46
46
|
- Secure handling of sensitive data
|
47
|
+
- Model registry management with support for updating to the latest OpenAI models
|
48
|
+
- Non-intrusive registry update checks with user notifications
|
47
49
|
|
48
50
|
## Requirements
|
49
51
|
|
@@ -63,6 +65,16 @@ pip install ostruct-cli
|
|
63
65
|
|
64
66
|
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
67
|
|
68
|
+
## Environment Variables
|
69
|
+
|
70
|
+
ostruct-cli respects the following environment variables:
|
71
|
+
|
72
|
+
- `OPENAI_API_KEY`: Your OpenAI API key (required unless provided via command line)
|
73
|
+
- `OPENAI_API_BASE`: Custom API base URL (optional)
|
74
|
+
- `OPENAI_API_VERSION`: API version to use (optional)
|
75
|
+
- `OPENAI_API_TYPE`: API type (e.g., "azure") (optional)
|
76
|
+
- `OSTRUCT_DISABLE_UPDATE_CHECKS`: Set to "1", "true", or "yes" to disable automatic registry update checks
|
77
|
+
|
66
78
|
## Shell Completion
|
67
79
|
|
68
80
|
ostruct-cli supports shell completion for Bash, Zsh, and Fish shells. To enable it:
|
@@ -264,3 +276,35 @@ ostruct run template.j2 schema.json --sys-prompt "Override prompt"
|
|
264
276
|
ostruct run template.j2 schema.json --ignore-task-sysprompt
|
265
277
|
```
|
266
278
|
|
279
|
+
## Model Registry Management
|
280
|
+
|
281
|
+
ostruct-cli maintains a registry of OpenAI models and their capabilities, which includes:
|
282
|
+
|
283
|
+
- Context window sizes for each model
|
284
|
+
- Maximum output token limits
|
285
|
+
- Supported parameters and their constraints
|
286
|
+
- Model version information
|
287
|
+
|
288
|
+
To ensure you're using the latest models and features, you can update the registry:
|
289
|
+
|
290
|
+
```bash
|
291
|
+
# Update from the official repository
|
292
|
+
ostruct update-registry
|
293
|
+
|
294
|
+
# Update from a custom URL
|
295
|
+
ostruct update-registry --url https://example.com/models.yml
|
296
|
+
|
297
|
+
# Force an update even if the registry is current
|
298
|
+
ostruct update-registry --force
|
299
|
+
```
|
300
|
+
|
301
|
+
This is especially useful when:
|
302
|
+
|
303
|
+
- New OpenAI models are released
|
304
|
+
- Model capabilities or parameters change
|
305
|
+
- You need to work with custom model configurations
|
306
|
+
|
307
|
+
The registry file is stored at `~/.openai_structured/config/models.yml` and is automatically referenced when validating model parameters and token limits.
|
308
|
+
|
309
|
+
The update command uses HTTP conditional requests (If-Modified-Since headers) to check if the remote registry has changed before downloading, ensuring efficient updates.
|
310
|
+
|
@@ -1,8 +1,8 @@
|
|
1
1
|
ostruct/__init__.py,sha256=X6zo6V7ZNMv731Wi388aTVQngD1410ExGwGx4J6lpyo,187
|
2
|
-
ostruct/cli/__init__.py,sha256=
|
2
|
+
ostruct/cli/__init__.py,sha256=IfQ68CRqPEeCP4zxV1DxowSEWFKhZiB8swMAOZJOXa8,494
|
3
3
|
ostruct/cli/base_errors.py,sha256=S1cQxoiALbXKPxzgLo6XdSWpzPRb7RKz0QARmu9Zt4g,5987
|
4
4
|
ostruct/cli/cache_manager.py,sha256=ej3KrRfkKKZ_lEp2JswjbJ5bW2ncsvna9NeJu81cqqs,5192
|
5
|
-
ostruct/cli/cli.py,sha256=
|
5
|
+
ostruct/cli/cli.py,sha256=WqVToFC4mt4qXeaW0pGRmakGal8vxxH0ny_BHNMKh5M,69645
|
6
6
|
ostruct/cli/click_options.py,sha256=WbRJdB9sO63ChN3fnCP7XWs73DHKl0C1ervfwL11am0,11371
|
7
7
|
ostruct/cli/errors.py,sha256=UytzS6RKOFMpB3GCJl73M93k74cutKOTFo5RDM4PTE0,15292
|
8
8
|
ostruct/cli/exit_codes.py,sha256=uNjvQeUGwU1mlUJYIDrExAn7YlwOXZo603yLAwpqIwk,338
|
@@ -12,6 +12,7 @@ ostruct/cli/file_utils.py,sha256=J3-6fbEGQ7KD_bU81pAxueHLv9XV0X7f8FSMt_0AJGQ,225
|
|
12
12
|
ostruct/cli/model_creation.py,sha256=UdXT3657rC5t-aZAZkQDuIdaQWSY9P6vgHKNAoFUWVw,16768
|
13
13
|
ostruct/cli/path_utils.py,sha256=j44q1OoLkqMErgK-qEuhuIZ1VyzqRIvNgxR1et9PoXA,4813
|
14
14
|
ostruct/cli/progress.py,sha256=rj9nVEco5UeZORMbzd7mFJpFGJjbH9KbBFh5oTE5Anw,3415
|
15
|
+
ostruct/cli/registry_updates.py,sha256=H0-Ftz4TvzryS2Qyoei7k2GKiY_s0Qr17i6pqrRmgF0,4921
|
15
16
|
ostruct/cli/schema_validation.py,sha256=ohEuxJ0KF93qphj0JSZDnrxDn0C2ZU37g-U2JY03onM,8154
|
16
17
|
ostruct/cli/security/__init__.py,sha256=CQpkCgTFYlA1p6atpQeNgIKtE4LZGUKt4EbytbGKpCs,846
|
17
18
|
ostruct/cli/security/allowed_checker.py,sha256=N5UXlpjdj5zAbKk-lRDlHiHV3KtQHtJNhtZI_qGB4zw,1638
|
@@ -37,8 +38,8 @@ ostruct/cli/token_utils.py,sha256=r4KPEO3Sec18Q6mU0aClK6XGShvusgUggXEQgEPPlaA,13
|
|
37
38
|
ostruct/cli/utils.py,sha256=uY7c0NaINHWfnl77FcPE3TmYUXv3RqEeUTjrCMDij9A,922
|
38
39
|
ostruct/cli/validators.py,sha256=k-vmBjkPmC-VwSTM5Yq1zQjGSIOzzTHS4kc0B7Aqpck,3186
|
39
40
|
ostruct/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
ostruct_cli-0.
|
41
|
-
ostruct_cli-0.
|
42
|
-
ostruct_cli-0.
|
43
|
-
ostruct_cli-0.
|
44
|
-
ostruct_cli-0.
|
41
|
+
ostruct_cli-0.7.0.dist-info/LICENSE,sha256=QUOY6QCYVxAiH8vdrUTDqe3i9hQ5bcNczppDSVpLTjk,1068
|
42
|
+
ostruct_cli-0.7.0.dist-info/METADATA,sha256=1Z54LbqNhr6aRMLvoiTKeaeETc5fNQe31J7Msz4EmyA,8618
|
43
|
+
ostruct_cli-0.7.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
44
|
+
ostruct_cli-0.7.0.dist-info/entry_points.txt,sha256=NFq9IuqHVTem0j9zKjV8C1si_zGcP1RL6Wbvt9fUDXw,48
|
45
|
+
ostruct_cli-0.7.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|