ostruct-cli 0.1.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/__init__.py +0 -0
- ostruct/cli/__init__.py +19 -0
- ostruct/cli/cache_manager.py +175 -0
- ostruct/cli/cli.py +2033 -0
- ostruct/cli/errors.py +329 -0
- ostruct/cli/file_info.py +316 -0
- ostruct/cli/file_list.py +151 -0
- ostruct/cli/file_utils.py +518 -0
- ostruct/cli/path_utils.py +123 -0
- ostruct/cli/progress.py +105 -0
- ostruct/cli/security.py +311 -0
- ostruct/cli/security_types.py +49 -0
- ostruct/cli/template_env.py +55 -0
- ostruct/cli/template_extensions.py +51 -0
- ostruct/cli/template_filters.py +650 -0
- ostruct/cli/template_io.py +261 -0
- ostruct/cli/template_rendering.py +347 -0
- ostruct/cli/template_schema.py +565 -0
- ostruct/cli/template_utils.py +288 -0
- ostruct/cli/template_validation.py +375 -0
- ostruct/cli/utils.py +31 -0
- ostruct/py.typed +0 -0
- ostruct_cli-0.1.0.dist-info/LICENSE +21 -0
- ostruct_cli-0.1.0.dist-info/METADATA +182 -0
- ostruct_cli-0.1.0.dist-info/RECORD +27 -0
- ostruct_cli-0.1.0.dist-info/WHEEL +4 -0
- ostruct_cli-0.1.0.dist-info/entry_points.txt +3 -0
ostruct/__init__.py
ADDED
File without changes
|
ostruct/cli/__init__.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
"""Command-line interface for making structured OpenAI API calls."""
|
2
|
+
|
3
|
+
from .cli import (
|
4
|
+
ExitCode,
|
5
|
+
_main,
|
6
|
+
validate_schema_file,
|
7
|
+
validate_task_template,
|
8
|
+
validate_variable_mapping,
|
9
|
+
)
|
10
|
+
from .path_utils import validate_path_mapping
|
11
|
+
|
12
|
+
__all__ = [
|
13
|
+
"ExitCode",
|
14
|
+
"_main",
|
15
|
+
"validate_path_mapping",
|
16
|
+
"validate_schema_file",
|
17
|
+
"validate_task_template",
|
18
|
+
"validate_variable_mapping",
|
19
|
+
]
|
@@ -0,0 +1,175 @@
|
|
1
|
+
"""Cache management for file content.
|
2
|
+
|
3
|
+
This module provides a thread-safe cache manager for file content
|
4
|
+
with LRU eviction and automatic invalidation on file changes.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from dataclasses import dataclass
|
9
|
+
from typing import Any, Optional, Tuple
|
10
|
+
|
11
|
+
from cachetools import LRUCache
|
12
|
+
from cachetools.keys import hashkey
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
# Type alias for cache keys
|
17
|
+
CacheKey = Tuple[Any, ...]
|
18
|
+
|
19
|
+
|
20
|
+
@dataclass(frozen=True)
|
21
|
+
class CacheEntry:
|
22
|
+
"""Represents a cached file entry.
|
23
|
+
|
24
|
+
Note: This class is immutable (frozen) to ensure thread safety
|
25
|
+
when used as a cache value.
|
26
|
+
"""
|
27
|
+
|
28
|
+
content: str
|
29
|
+
encoding: Optional[str]
|
30
|
+
hash_value: Optional[str]
|
31
|
+
mtime_ns: int # Nanosecond precision mtime
|
32
|
+
size: int # Actual file size from stat
|
33
|
+
|
34
|
+
|
35
|
+
class FileCache:
|
36
|
+
"""Thread-safe LRU cache for file content with size limit."""
|
37
|
+
|
38
|
+
def __init__(self, max_size_bytes: int = 50 * 1024 * 1024): # 50MB default
|
39
|
+
"""Initialize cache with maximum size in bytes.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
max_size_bytes: Maximum cache size in bytes
|
43
|
+
"""
|
44
|
+
self._max_size = max_size_bytes
|
45
|
+
self._current_size = 0
|
46
|
+
self._cache: LRUCache[CacheKey, CacheEntry] = LRUCache(maxsize=1024)
|
47
|
+
logger.debug(
|
48
|
+
"Initialized FileCache with max_size=%d bytes, maxsize=%d entries",
|
49
|
+
max_size_bytes,
|
50
|
+
1024,
|
51
|
+
)
|
52
|
+
|
53
|
+
def _remove_entry(self, key: CacheKey) -> None:
|
54
|
+
"""Remove entry from cache and update size.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
key: Cache key to remove
|
58
|
+
"""
|
59
|
+
entry = self._cache.get(key)
|
60
|
+
if entry is not None:
|
61
|
+
self._current_size -= entry.size
|
62
|
+
logger.debug(
|
63
|
+
"Removed cache entry: key=%s, size=%d, new_total_size=%d",
|
64
|
+
key,
|
65
|
+
entry.size,
|
66
|
+
self._current_size,
|
67
|
+
)
|
68
|
+
self._cache.pop(key, None)
|
69
|
+
|
70
|
+
def get(
|
71
|
+
self, path: str, current_mtime_ns: int, current_size: int
|
72
|
+
) -> Optional[CacheEntry]:
|
73
|
+
"""Get cache entry if it exists and is valid.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
path: Absolute path to the file
|
77
|
+
current_mtime_ns: Current modification time in nanoseconds
|
78
|
+
current_size: Current file size in bytes
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
CacheEntry if valid cache exists, None otherwise
|
82
|
+
"""
|
83
|
+
key = hashkey(path)
|
84
|
+
entry = self._cache.get(key)
|
85
|
+
|
86
|
+
if entry is None:
|
87
|
+
logger.debug("Cache miss for %s: no entry found", path)
|
88
|
+
return None
|
89
|
+
|
90
|
+
# Check if file has been modified using both mtime and size
|
91
|
+
if entry.mtime_ns != current_mtime_ns or entry.size != current_size:
|
92
|
+
logger.info(
|
93
|
+
"Cache invalidated for %s: mtime_ns=%d->%d (%s), size=%d->%d (%s)",
|
94
|
+
path,
|
95
|
+
entry.mtime_ns,
|
96
|
+
current_mtime_ns,
|
97
|
+
"changed" if entry.mtime_ns != current_mtime_ns else "same",
|
98
|
+
entry.size,
|
99
|
+
current_size,
|
100
|
+
"changed" if entry.size != current_size else "same",
|
101
|
+
)
|
102
|
+
self._remove_entry(key)
|
103
|
+
return None
|
104
|
+
|
105
|
+
logger.debug(
|
106
|
+
"Cache hit for %s: mtime_ns=%d, size=%d",
|
107
|
+
path,
|
108
|
+
entry.mtime_ns,
|
109
|
+
entry.size,
|
110
|
+
)
|
111
|
+
return entry
|
112
|
+
|
113
|
+
def put(
|
114
|
+
self,
|
115
|
+
path: str,
|
116
|
+
content: str,
|
117
|
+
encoding: Optional[str],
|
118
|
+
hash_value: Optional[str],
|
119
|
+
mtime_ns: int,
|
120
|
+
size: int,
|
121
|
+
) -> None:
|
122
|
+
"""Add or update cache entry.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
path: Absolute path to the file
|
126
|
+
content: File content
|
127
|
+
encoding: File encoding
|
128
|
+
hash_value: Content hash
|
129
|
+
mtime_ns: File modification time in nanoseconds
|
130
|
+
size: File size in bytes from stat
|
131
|
+
"""
|
132
|
+
if size > self._max_size:
|
133
|
+
logger.warning(
|
134
|
+
"File %s size (%d bytes) exceeds cache max size (%d bytes)",
|
135
|
+
path,
|
136
|
+
size,
|
137
|
+
self._max_size,
|
138
|
+
)
|
139
|
+
return
|
140
|
+
|
141
|
+
key = hashkey(path)
|
142
|
+
self._remove_entry(key)
|
143
|
+
|
144
|
+
entry = CacheEntry(content, encoding, hash_value, mtime_ns, size)
|
145
|
+
|
146
|
+
# Evict entries if needed
|
147
|
+
evicted_count = 0
|
148
|
+
while self._current_size + size > self._max_size and self._cache:
|
149
|
+
evicted_key, evicted = self._cache.popitem()
|
150
|
+
self._current_size -= evicted.size
|
151
|
+
evicted_count += 1
|
152
|
+
logger.debug(
|
153
|
+
"Evicted cache entry: key=%s, size=%d, new_total_size=%d",
|
154
|
+
evicted_key,
|
155
|
+
evicted.size,
|
156
|
+
self._current_size,
|
157
|
+
)
|
158
|
+
|
159
|
+
if evicted_count > 0:
|
160
|
+
logger.info(
|
161
|
+
"Evicted %d entries to make room for %s (size=%d)",
|
162
|
+
evicted_count,
|
163
|
+
path,
|
164
|
+
size,
|
165
|
+
)
|
166
|
+
|
167
|
+
self._cache[key] = entry
|
168
|
+
self._current_size += size
|
169
|
+
logger.debug(
|
170
|
+
"Added cache entry: path=%s, size=%d, total_size=%d/%d",
|
171
|
+
path,
|
172
|
+
size,
|
173
|
+
self._current_size,
|
174
|
+
self._max_size,
|
175
|
+
)
|