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 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 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ostruct-cli
3
- Version: 0.6.2
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.0.0,<3.0.0)
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=sYHKT6o1kFy1acbXejzAvVm8Cy8U91Yf1l4DlzquHKg,409
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=6oxMjJmkKv71h6G7-D6O_HN40JsD55Y3SnH8Bdb8h58,66751
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.6.2.dist-info/LICENSE,sha256=QUOY6QCYVxAiH8vdrUTDqe3i9hQ5bcNczppDSVpLTjk,1068
41
- ostruct_cli-0.6.2.dist-info/METADATA,sha256=F3wwhPgaWxUtKDeCrtL2W3q6xhQ2DFh7vFC_8Bqd4jE,6949
42
- ostruct_cli-0.6.2.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
43
- ostruct_cli-0.6.2.dist-info/entry_points.txt,sha256=NFq9IuqHVTem0j9zKjV8C1si_zGcP1RL6Wbvt9fUDXw,48
44
- ostruct_cli-0.6.2.dist-info/RECORD,,
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,,