ostruct-cli 0.8.2__py3-none-any.whl → 0.8.3__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.
@@ -50,6 +50,7 @@ Notes:
50
50
  """
51
51
 
52
52
  import logging
53
+ import re
53
54
  from typing import (
54
55
  Any,
55
56
  Callable,
@@ -150,6 +151,45 @@ def find_loop_vars(nodes: List[Node]) -> Set[str]:
150
151
  return loop_vars
151
152
 
152
153
 
154
+ def _extract_variable_name_from_jinja_error(error_message: str) -> str:
155
+ """Extract the actual variable name from a Jinja2 UndefinedError message.
156
+
157
+ Handles various Jinja2 error message formats:
158
+ - "'variable_name' is undefined"
159
+ - "'object_description' has no attribute 'property_name'"
160
+ - Other formats
161
+
162
+ Args:
163
+ error_message: The string representation of the Jinja2 UndefinedError
164
+
165
+ Returns:
166
+ The extracted variable name, or a sanitized version if parsing fails
167
+ """
168
+ # Pattern 1: Standard undefined variable: "'variable_name' is undefined"
169
+ match = re.match(r"'([^']+)' is undefined", error_message)
170
+ if match:
171
+ return match.group(1)
172
+
173
+ # Pattern 2: Attribute access on object: "'object' has no attribute 'property'"
174
+ # In this case, we want to extract just the property name, not the object description
175
+ match = re.match(r"'[^']+' has no attribute '([^']+)'", error_message)
176
+ if match:
177
+ property_name = match.group(1)
178
+ return property_name
179
+
180
+ # Pattern 3: Try to find any quoted identifier that looks like a variable name
181
+ # Look for quoted strings that contain only valid Python identifier characters
182
+ quoted_parts: List[str] = re.findall(r"'([^']+)'", error_message)
183
+ for part in quoted_parts:
184
+ # Check if this looks like a variable name (not a class name or description)
185
+ if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", part) and "." not in part:
186
+ return part
187
+
188
+ # Fallback: If we can't parse it properly, return a generic message
189
+ # This avoids exposing internal class names to users
190
+ return "unknown_variable"
191
+
192
+
153
193
  def validate_template_placeholders(
154
194
  template: str, template_context: Optional[Dict[str, Any]] = None
155
195
  ) -> None:
@@ -343,9 +383,7 @@ def validate_template_placeholders(
343
383
  env.from_string(template).render(validation_context)
344
384
  except jinja2.UndefinedError as e:
345
385
  # Convert Jinja2 undefined errors to TaskTemplateVariableError with helpful message
346
- var_name = str(e).split("'")[
347
- 1
348
- ] # Extract variable name from error message
386
+ var_name = _extract_variable_name_from_jinja_error(str(e))
349
387
  error_msg = (
350
388
  f"Missing required template variable: {var_name}\n"
351
389
  f"Available variables: {', '.join(sorted(available_vars))}\n"
ostruct/cli/types.py CHANGED
@@ -35,7 +35,6 @@ class CLIParams(TypedDict, total=False):
35
35
  no_progress: bool
36
36
  api_key: Optional[str]
37
37
  verbose: bool
38
- debug_openai_stream: bool
39
38
  show_model_schema: bool
40
39
  debug_validation: bool
41
40
  temperature: Optional[float]
@@ -76,3 +75,17 @@ class CLIParams(TypedDict, total=False):
76
75
  tool_files: List[
77
76
  Tuple[str, str]
78
77
  ] # List of (tool, path) tuples from --file-for
78
+ web_search: bool
79
+ debug: bool
80
+ show_templates: bool
81
+ debug_templates: bool
82
+ show_context: bool
83
+ show_context_detailed: bool
84
+ show_pre_optimization: bool
85
+ show_optimization_diff: bool
86
+ no_optimization: bool
87
+ show_optimization_steps: bool
88
+ optimization_step_detail: str
89
+ help_debug: bool
90
+ enabled_features: List[str] # List of feature names to enable
91
+ disabled_features: List[str] # List of feature names to disable
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ostruct-cli
3
- Version: 0.8.2
3
+ Version: 0.8.3
4
4
  Summary: CLI for OpenAI Structured Output with Multi-Tool Integration
5
5
  Author: Yaniv Golan
6
6
  Author-email: yaniv@golan.name
@@ -22,6 +22,7 @@ Requires-Dist: chardet (>=5.0.0,<6.0)
22
22
  Requires-Dist: click (>=8.1.7,<9.0)
23
23
  Requires-Dist: flake8 (>=6.0,<7.0) ; extra == "dev"
24
24
  Requires-Dist: flake8-pyproject (>=1.2.3,<2.0) ; extra == "dev"
25
+ Requires-Dist: hypothesis (>=6.0.0,<7.0) ; extra == "dev"
25
26
  Requires-Dist: ijson (>=3.2.3,<4.0)
26
27
  Requires-Dist: jinja2 (>=3.1.2,<4.0)
27
28
  Requires-Dist: jsonschema (>=4.23.0,<5.0)
@@ -37,6 +38,7 @@ Requires-Dist: pygments (>=2.15.0,<3.0)
37
38
  Requires-Dist: pytest (>=8.3.4,<9.0) ; extra == "dev"
38
39
  Requires-Dist: pytest-asyncio (>=0.25.2,<1.0) ; extra == "dev"
39
40
  Requires-Dist: pytest-mock (>=3.14.0,<4.0) ; extra == "dev"
41
+ Requires-Dist: pytest-rerunfailures (>=12.0,<13.0) ; extra == "dev"
40
42
  Requires-Dist: python-dotenv (>=1.0.1,<2.0) ; extra == "dev"
41
43
  Requires-Dist: pyyaml (>=6.0.2,<7.0)
42
44
  Requires-Dist: sphinx (>=7.0,<8.0) ; extra == "dev"
@@ -302,7 +304,7 @@ ostruct run analysis.j2 schema.json -fc data.csv
302
304
  ostruct run search.j2 schema.json -fs documentation.pdf
303
305
 
304
306
  # Web Search (real-time information)
305
- ostruct run research.j2 schema.json --web-search -V topic="latest AI developments"
307
+ ostruct run research.j2 schema.json --enable-tool web-search -V topic="latest AI developments"
306
308
 
307
309
  # Multiple tools with one file
308
310
  ostruct run template.j2 schema.json --file-for code-interpreter shared.json --file-for file-search shared.json
@@ -370,6 +372,7 @@ tools:
370
372
  code_interpreter:
371
373
  auto_download: true
372
374
  output_directory: "./output"
375
+ download_strategy: "two_pass_sentinel" # Enable reliable file downloads
373
376
 
374
377
  mcp:
375
378
  custom_server: "https://my-mcp-server.com"
@@ -384,6 +387,35 @@ Load custom configuration:
384
387
  ostruct --config my-config.yaml run template.j2 schema.json
385
388
  ```
386
389
 
390
+ ### Code Interpreter File Downloads
391
+
392
+ **Important**: If you're using Code Interpreter with structured output (JSON schemas), you may need to enable the two-pass download strategy to ensure files are downloaded reliably.
393
+
394
+ #### Option 1: CLI Flags (Recommended for one-off usage)
395
+
396
+ ```bash
397
+ # Enable reliable file downloads for this run
398
+ ostruct run template.j2 schema.json -fc data.csv --enable-feature ci-download-hack
399
+
400
+ # Force single-pass mode (override config)
401
+ ostruct run template.j2 schema.json -fc data.csv --disable-feature ci-download-hack
402
+ ```
403
+
404
+ #### Option 2: Configuration File (Recommended for persistent settings)
405
+
406
+ ```yaml
407
+ # ostruct.yaml
408
+ tools:
409
+ code_interpreter:
410
+ download_strategy: "two_pass_sentinel" # Enables reliable file downloads
411
+ auto_download: true
412
+ output_directory: "./downloads"
413
+ ```
414
+
415
+ **Why this is needed**: OpenAI's structured output mode can prevent file download annotations from being generated. The two-pass strategy works around this by making two API calls: one to generate files (without structured output), then another to ensure schema compliance. For detailed technical information, see [docs/known-issues/2025-06-responses-ci-file-output.md](docs/known-issues/2025-06-responses-ci-file-output.md).
416
+
417
+ **Performance**: The two-pass strategy approximately doubles token usage but ensures reliable file downloads when using structured output with Code Interpreter.
418
+
387
419
  ## Get Started Quickly
388
420
 
389
421
  🚀 **New to ostruct?** Follow our [step-by-step quickstart guide](https://ostruct.readthedocs.io/en/latest/user-guide/quickstart.html) featuring Juno the beagle for a hands-on introduction.
@@ -628,6 +660,60 @@ The registry file is stored at `~/.openai_structured/config/models.yml` and is a
628
660
 
629
661
  The update command uses HTTP conditional requests (If-Modified-Since headers) to check if the remote registry has changed before downloading, ensuring efficient updates.
630
662
 
663
+ # Testing
664
+
665
+ ## Running Tests
666
+
667
+ The test suite is divided into two categories:
668
+
669
+ ### Regular Tests (Default)
670
+
671
+ ```bash
672
+ # Run all tests (skips live tests by default)
673
+ pytest
674
+
675
+ # Run specific test file
676
+ pytest tests/test_config.py
677
+
678
+ # Run with verbose output
679
+ pytest -v
680
+ ```
681
+
682
+ ### Live Tests
683
+
684
+ Live tests make real API calls to OpenAI and require a valid API key. They are skipped by default.
685
+
686
+ ```bash
687
+ # Run only live tests (requires OPENAI_API_KEY)
688
+ pytest -m live
689
+
690
+ # Run all tests including live tests
691
+ pytest -m "live or not live"
692
+
693
+ # Run specific live test
694
+ pytest tests/test_responses_annotations.py -m live
695
+ ```
696
+
697
+ **Live tests include:**
698
+
699
+ - Tests that make actual OpenAI API calls
700
+ - Tests that run `ostruct` commands via subprocess
701
+ - Tests that verify real API behavior and file downloads
702
+
703
+ **Requirements for live tests:**
704
+
705
+ - Valid `OPENAI_API_KEY` environment variable
706
+ - Internet connection
707
+ - May incur API costs
708
+
709
+ ## Test Markers
710
+
711
+ - `@pytest.mark.live` - Tests that make real API calls or run actual commands
712
+ - `@pytest.mark.no_fs` - Tests that need real filesystem (not pyfakefs)
713
+ - `@pytest.mark.slow` - Performance/stress tests
714
+ - `@pytest.mark.flaky` - Tests that may need reruns
715
+ - `@pytest.mark.mock_openai` - Tests using mocked OpenAI client
716
+
631
717
  <!--
632
718
  MAINTAINER NOTE: After editing this README, please test GitHub rendering by:
633
719
  1. Creating a draft PR or pushing to a test branch
@@ -3,31 +3,32 @@ ostruct/cli/__init__.py,sha256=e-DtWRviCr3fIAJH4cB4UAvles3-rqnhJaTOlBn9TKs,871
3
3
  ostruct/cli/base_errors.py,sha256=o-877bJJA8yJWISRPy0KyL6wDu1-_avddmQIfVePuFM,5989
4
4
  ostruct/cli/cache_manager.py,sha256=ej3KrRfkKKZ_lEp2JswjbJ5bW2ncsvna9NeJu81cqqs,5192
5
5
  ostruct/cli/cli.py,sha256=ZUkQ-iwk4KCKeetPMlvSDwxfXIe4h1-yP80SfJgvi_o,4098
6
- ostruct/cli/click_options.py,sha256=f2a6XTidy-RJgL64WAgTqRYpMPokY0O8Bb6aQgwMOtk,28298
7
- ostruct/cli/code_interpreter.py,sha256=sEq-zi0CfotRqEseA9pWTm-UU1fZTH_06WGvWPNwct8,7786
6
+ ostruct/cli/click_options.py,sha256=FbRMfoFgg5icdgewPS8cePSYv6yTnxbRxyDvfFDdRaI,31845
7
+ ostruct/cli/code_interpreter.py,sha256=lnnyEvUh2pGObN9ENpr-X4p0C0oIWiyG1CFQWW4akBQ,16726
8
8
  ostruct/cli/commands/__init__.py,sha256=3NHz-WZ9XqrnWWMksoV2MpYpHnjA-EO9lsrBOYeHcjY,723
9
9
  ostruct/cli/commands/list_models.py,sha256=yeuQpUGAmRr4uOHS7teuVHkC9dkqN0yKDOEw_n-ehi0,4662
10
10
  ostruct/cli/commands/quick_ref.py,sha256=zpD7kI3sfrcc9CiRkzWdASTNX5jAk3HFB7MilzlCsag,2004
11
- ostruct/cli/commands/run.py,sha256=1M5KYhXPLJLFVbfGJHRp88kO25CSN6TP1A0cVex1-7A,4370
11
+ ostruct/cli/commands/run.py,sha256=Cm9Yuf0DLt5CqKfgAYubhQRcvLdK1vqIIuz_ynjfhQ4,6640
12
12
  ostruct/cli/commands/update_registry.py,sha256=7DQrPlCJScPVgE2HbFAM7UMap-EdYu58AQWfpI-H7Gw,2483
13
- ostruct/cli/config.py,sha256=keX5mUiyZ9_CrVHZFpxr7loBAetORRW-ZjNUoQFa_k8,9030
13
+ ostruct/cli/config.py,sha256=7tKI1gWLpTISn5OorGWIx66N1J7XW2aq30hPNISZzQ0,9958
14
14
  ostruct/cli/cost_estimation.py,sha256=08hyE-kM5QYzj7y-KB3lMD_RxCMoM_Ve3-IQlSpJAo4,4483
15
- ostruct/cli/errors.py,sha256=WUa_LwwBMynrP9WVgS199ovir1XJFLZIRw5QoPRLVwY,25145
15
+ ostruct/cli/errors.py,sha256=sVOM_ZKvrtnmT8_WR-DfX2crs6HmvmwrbNQlxm-5_Ew,24669
16
16
  ostruct/cli/exit_codes.py,sha256=gdvB1dpu0LalEUZrofpk5K6aTQ24n5AfkAK5umludHU,365
17
17
  ostruct/cli/explicit_file_processor.py,sha256=B6yUPbyn6MVd81GcyMVpORFwyaHFFESLwFixp2B6M5w,19767
18
18
  ostruct/cli/field_utils.py,sha256=bcRi1qQ0Ac2UCfrKSQ677_yu-VzaShm_zN7QLf98qc0,1939
19
- ostruct/cli/file_info.py,sha256=Vgx-unid2ODSYbJxzgC2ndEeh-DS6eADsrqG34_C820,16182
19
+ ostruct/cli/file_info.py,sha256=s8AHPtyU3__2LRJCCIEPuODYM43is4Y2Q_VDhBOl3XU,17395
20
20
  ostruct/cli/file_list.py,sha256=alRAguq4tj1zH0_qlWaRoyHo1G1Xmxqu9Xd4QP-zYP0,20268
21
21
  ostruct/cli/file_search.py,sha256=N12mkji2ttvItLVPyAWE3KEfhTv8hV5IXPrQME2UFdE,15313
22
- ostruct/cli/file_utils.py,sha256=kLZKiZadkIiYvs3B_gngqf_PwMRMYIWw49i8GTGVbkk,23607
22
+ ostruct/cli/file_utils.py,sha256=JZprQ-1LHQzI3eBfeCIS6VmxTa2fGUZHygGC8gcwpJM,24367
23
+ ostruct/cli/json_extract.py,sha256=ZleIxat8g-JnA9VVqWgJaKxN7QzL25itQ8aP0Gb5e4Q,2650
23
24
  ostruct/cli/mcp_integration.py,sha256=EXIz_KbYC4srWZxuKVkINeaHgHvEYB9l1uhptpLNn5I,18774
24
- ostruct/cli/model_creation.py,sha256=l-FHxCaAmZ2akKszoRg8gf1KgJxeT10Pt8YX39bXeT4,23261
25
+ ostruct/cli/model_creation.py,sha256=HGo8Qv7eBF8Co463IR7RcbTCQcaOvd_cBGuRodRCAa4,23261
25
26
  ostruct/cli/model_validation.py,sha256=j2az3q88-Ljm2cMMgZ8p_-gcp1vKQnWCknnw0y0YlAw,6675
26
27
  ostruct/cli/path_utils.py,sha256=j44q1OoLkqMErgK-qEuhuIZ1VyzqRIvNgxR1et9PoXA,4813
27
28
  ostruct/cli/progress.py,sha256=rj9nVEco5UeZORMbzd7mFJpFGJjbH9KbBFh5oTE5Anw,3415
28
29
  ostruct/cli/progress_reporting.py,sha256=MBjALM4pmPd_d9YuXqH162-tkC6DDKYmz-pJPSGLTfk,13669
29
30
  ostruct/cli/registry_updates.py,sha256=ohiHdlfrocvThpR_ZjMyqulDKFjRM1hIFKOlNzpaqHg,5138
30
- ostruct/cli/runner.py,sha256=pNw0TzdpDElzaUYxb8R-rzSPqa1MOzUM-hPaOvpETI4,54045
31
+ ostruct/cli/runner.py,sha256=_xiT9_ywk5lNz3P1qFpqOcJGwXqAE4xQ8Zxc9p6x0Ko,65192
31
32
  ostruct/cli/schema_utils.py,sha256=9LnsjxEKg6RIfXQB3nS3pyDggm5n-4-thXf92897gJU,3590
32
33
  ostruct/cli/schema_validation.py,sha256=ohEuxJ0KF93qphj0JSZDnrxDn0C2ZU37g-U2JY03onM,8154
33
34
  ostruct/cli/security/__init__.py,sha256=CQpkCgTFYlA1p6atpQeNgIKtE4LZGUKt4EbytbGKpCs,846
@@ -41,6 +42,7 @@ ostruct/cli/security/security_manager.py,sha256=HFCqJ5kAhaZlFnPTEs6MKNM8JeE2q79d
41
42
  ostruct/cli/security/symlink_resolver.py,sha256=wtZdJ_T_0FOy6B1P5ty1odEXQk9vr8BzlWeAFD4huJE,16744
42
43
  ostruct/cli/security/types.py,sha256=15yuG_T4CXyAFFFdSWLjVS7ACmDGIPXhQpZ8awcDwCQ,2991
43
44
  ostruct/cli/security/windows_paths.py,sha256=qxC2H2kLwtmQ7YePYde3UrmOJcGnsLEebDLh242sUaI,13453
45
+ ostruct/cli/sentinel.py,sha256=69faYPrhVJmEpYNLsCtf1HF96aan3APqXZdIjxBNZYo,798
44
46
  ostruct/cli/serialization.py,sha256=ec0UswDE2onwtZVUoZaMCsGv6zW_tSKdBng2qVo6Ucs,704
45
47
  ostruct/cli/services.py,sha256=nLYUbF3DDNuilh7j9q_atUOjTAWta7bxTS3G-zkveaA,21621
46
48
  ostruct/cli/template_debug.py,sha256=1tP3pTdvQwHcMneYnpWnS2Jr5EQijjyCSQDh5DPXvDE,24698
@@ -49,21 +51,21 @@ ostruct/cli/template_env.py,sha256=7ZcGKyqlkV-ZS2sSUvzyaLsnlvPmfCKUG0epIV8TD6o,1
49
51
  ostruct/cli/template_extensions.py,sha256=_lomtDGMGxMfpw05v_-daJ0JbhRm_r_-uEJlPAjbpkI,2699
50
52
  ostruct/cli/template_filters.py,sha256=S9ad8she0lXRr0PDQVkNrPWbZg7GnybptEXUMkQKOyo,20657
51
53
  ostruct/cli/template_io.py,sha256=yUWO-8rZnSdX97DTMSEX8fG9CP1ISsOhm2NZN3Fab9A,8821
52
- ostruct/cli/template_optimizer.py,sha256=_eQbHQP6fH5tbWLwXtLSLDYlGQ8hLpZKVgXsbcWF2xY,16394
53
- ostruct/cli/template_processor.py,sha256=AWtWEpJvCCFB1PzoDp6EovV4RaIMbY316cb-Iq5o48c,41726
54
- ostruct/cli/template_rendering.py,sha256=5i7gtFjW2QKVW3e_xK0Llrurn0I4e_gG0gzn2p9IFTc,15030
54
+ ostruct/cli/template_optimizer.py,sha256=gwngZ5XrIkU5BrjFibWT0VRf9WrH6NS4SAlrUzme1C8,16684
55
+ ostruct/cli/template_processor.py,sha256=hp9_ZQmVdN6JwCSQJoHB1mVq_p0sTZSojb7c4Y4Vjvs,47301
56
+ ostruct/cli/template_rendering.py,sha256=41f6An7cpI04z2qyesmnXgPVMD1je3ePA6Xz8uuA06U,16705
55
57
  ostruct/cli/template_schema.py,sha256=ckH4rUZnEgfm_BHS9LnMGr8LtDxRmZ0C6UBVrSp8KTc,19604
56
58
  ostruct/cli/template_utils.py,sha256=MZdXXjL-x-IXX-5Y8GWopGNBkDE2ItLdCuCl0QWFR_U,14968
57
- ostruct/cli/template_validation.py,sha256=AXa2zmsws1j-0CTFlp7fMiZR43iNLnj4h467up2JdgU,12693
59
+ ostruct/cli/template_validation.py,sha256=9L1JgM87NIV8JZUoDUT4dRjSDfBl9L8Yl2aVL_ahkmA,14294
58
60
  ostruct/cli/token_utils.py,sha256=r4KPEO3Sec18Q6mU0aClK6XGShvusgUggXEQgEPPlaA,1369
59
61
  ostruct/cli/token_validation.py,sha256=gmyPJ7B2gC_jSx_1wKZq87DEoFulj23X1XnVpO_aRNA,9930
60
- ostruct/cli/types.py,sha256=vCZFBUkeL1QUBM5tTSjEWf_5BUttlyC40kFfpNfTrrY,2474
62
+ ostruct/cli/types.py,sha256=6nARJ4MF5HIank0t6KGU-PPHC0VpFh3R8fNJZBXwgbA,2903
61
63
  ostruct/cli/unattended_operation.py,sha256=kI95SSVJC_taxORXQYrce_qLEnuKc6edwn9tMOye-qs,9383
62
64
  ostruct/cli/utils.py,sha256=uY7c0NaINHWfnl77FcPE3TmYUXv3RqEeUTjrCMDij9A,922
63
65
  ostruct/cli/validators.py,sha256=lbxAUUVS5TPJ7HdYZ5yB7gUjJqfcClZCuh0oktoq0E0,15291
64
66
  ostruct/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
- ostruct_cli-0.8.2.dist-info/LICENSE,sha256=DmGAkaYzhrdzTB9Y2Rvfzd3mJiF9ZrTOhxN8t6wrfHA,1098
66
- ostruct_cli-0.8.2.dist-info/METADATA,sha256=-DJtx83jOezthaQNrOBZpoL_NEH9jbdgCIP1YXOSkGw,21976
67
- ostruct_cli-0.8.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
68
- ostruct_cli-0.8.2.dist-info/entry_points.txt,sha256=NFq9IuqHVTem0j9zKjV8C1si_zGcP1RL6Wbvt9fUDXw,48
69
- ostruct_cli-0.8.2.dist-info/RECORD,,
67
+ ostruct_cli-0.8.3.dist-info/LICENSE,sha256=DmGAkaYzhrdzTB9Y2Rvfzd3mJiF9ZrTOhxN8t6wrfHA,1098
68
+ ostruct_cli-0.8.3.dist-info/METADATA,sha256=mBsqO645XWKto3RgifluCxqkilYH0C14wWrm1Pa6P6I,24827
69
+ ostruct_cli-0.8.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
70
+ ostruct_cli-0.8.3.dist-info/entry_points.txt,sha256=NFq9IuqHVTem0j9zKjV8C1si_zGcP1RL6Wbvt9fUDXw,48
71
+ ostruct_cli-0.8.3.dist-info/RECORD,,