ostruct-cli 0.6.0__py3-none-any.whl → 0.6.1__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/cli.py +79 -342
- ostruct/cli/model_creation.py +507 -0
- {ostruct_cli-0.6.0.dist-info → ostruct_cli-0.6.1.dist-info}/METADATA +1 -1
- {ostruct_cli-0.6.0.dist-info → ostruct_cli-0.6.1.dist-info}/RECORD +7 -6
- {ostruct_cli-0.6.0.dist-info → ostruct_cli-0.6.1.dist-info}/LICENSE +0 -0
- {ostruct_cli-0.6.0.dist-info → ostruct_cli-0.6.1.dist-info}/WHEEL +0 -0
- {ostruct_cli-0.6.0.dist-info → ostruct_cli-0.6.1.dist-info}/entry_points.txt +0 -0
ostruct/cli/cli.py
CHANGED
@@ -5,7 +5,6 @@ import json
|
|
5
5
|
import logging
|
6
6
|
import os
|
7
7
|
import sys
|
8
|
-
from enum import Enum, IntEnum
|
9
8
|
from typing import (
|
10
9
|
Any,
|
11
10
|
AsyncGenerator,
|
@@ -20,12 +19,11 @@ from typing import (
|
|
20
19
|
TypeVar,
|
21
20
|
Union,
|
22
21
|
cast,
|
23
|
-
get_origin,
|
24
22
|
overload,
|
25
23
|
)
|
26
24
|
|
27
25
|
if sys.version_info >= (3, 11):
|
28
|
-
|
26
|
+
pass
|
29
27
|
|
30
28
|
from datetime import date, datetime, time
|
31
29
|
from pathlib import Path
|
@@ -48,15 +46,7 @@ from openai_structured.errors import (
|
|
48
46
|
StreamBufferError,
|
49
47
|
)
|
50
48
|
from openai_structured.model_registry import ModelRegistry
|
51
|
-
from pydantic import
|
52
|
-
AnyUrl,
|
53
|
-
BaseModel,
|
54
|
-
ConfigDict,
|
55
|
-
EmailStr,
|
56
|
-
Field,
|
57
|
-
ValidationError,
|
58
|
-
create_model,
|
59
|
-
)
|
49
|
+
from pydantic import AnyUrl, BaseModel, EmailStr, Field
|
60
50
|
from pydantic.fields import FieldInfo as FieldInfoType
|
61
51
|
from pydantic.functional_validators import BeforeValidator
|
62
52
|
from pydantic.types import constr
|
@@ -69,11 +59,8 @@ from .. import __version__ # noqa: F401 - Used in package metadata
|
|
69
59
|
from .errors import (
|
70
60
|
CLIError,
|
71
61
|
DirectoryNotFoundError,
|
72
|
-
FieldDefinitionError,
|
73
62
|
InvalidJSONError,
|
74
63
|
ModelCreationError,
|
75
|
-
ModelValidationError,
|
76
|
-
NestedModelError,
|
77
64
|
OstructFileNotFoundError,
|
78
65
|
PathSecurityError,
|
79
66
|
SchemaFileError,
|
@@ -86,6 +73,7 @@ from .errors import (
|
|
86
73
|
VariableValueError,
|
87
74
|
)
|
88
75
|
from .file_utils import FileInfoList, collect_files
|
76
|
+
from .model_creation import _create_enum_type, create_dynamic_model
|
89
77
|
from .path_utils import validate_path_mapping
|
90
78
|
from .security import SecurityManager
|
91
79
|
from .serialization import LogSerializer
|
@@ -97,6 +85,71 @@ from .token_utils import estimate_tokens_with_encoding
|
|
97
85
|
DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant."
|
98
86
|
|
99
87
|
|
88
|
+
# Validation functions
|
89
|
+
def pattern(regex: str) -> Any:
|
90
|
+
return constr(pattern=regex)
|
91
|
+
|
92
|
+
|
93
|
+
def min_length(length: int) -> Any:
|
94
|
+
return BeforeValidator(lambda v: v if len(str(v)) >= length else None)
|
95
|
+
|
96
|
+
|
97
|
+
def max_length(length: int) -> Any:
|
98
|
+
return BeforeValidator(lambda v: v if len(str(v)) <= length else None)
|
99
|
+
|
100
|
+
|
101
|
+
def ge(value: Union[int, float]) -> Any:
|
102
|
+
return BeforeValidator(lambda v: v if float(v) >= value else None)
|
103
|
+
|
104
|
+
|
105
|
+
def le(value: Union[int, float]) -> Any:
|
106
|
+
return BeforeValidator(lambda v: v if float(v) <= value else None)
|
107
|
+
|
108
|
+
|
109
|
+
def gt(value: Union[int, float]) -> Any:
|
110
|
+
return BeforeValidator(lambda v: v if float(v) > value else None)
|
111
|
+
|
112
|
+
|
113
|
+
def lt(value: Union[int, float]) -> Any:
|
114
|
+
return BeforeValidator(lambda v: v if float(v) < value else None)
|
115
|
+
|
116
|
+
|
117
|
+
def multiple_of(value: Union[int, float]) -> Any:
|
118
|
+
return BeforeValidator(lambda v: v if float(v) % value == 0 else None)
|
119
|
+
|
120
|
+
|
121
|
+
def create_template_context(
|
122
|
+
files: Optional[
|
123
|
+
Dict[str, Union[FileInfoList, str, List[str], Dict[str, str]]]
|
124
|
+
] = None,
|
125
|
+
variables: Optional[Dict[str, str]] = None,
|
126
|
+
json_variables: Optional[Dict[str, Any]] = None,
|
127
|
+
security_manager: Optional[SecurityManager] = None,
|
128
|
+
stdin_content: Optional[str] = None,
|
129
|
+
) -> Dict[str, Any]:
|
130
|
+
"""Create template context from files and variables."""
|
131
|
+
context: Dict[str, Any] = {}
|
132
|
+
|
133
|
+
# Add file variables
|
134
|
+
if files:
|
135
|
+
for name, file_list in files.items():
|
136
|
+
context[name] = file_list # Always keep FileInfoList wrapper
|
137
|
+
|
138
|
+
# Add simple variables
|
139
|
+
if variables:
|
140
|
+
context.update(variables)
|
141
|
+
|
142
|
+
# Add JSON variables
|
143
|
+
if json_variables:
|
144
|
+
context.update(json_variables)
|
145
|
+
|
146
|
+
# Add stdin if provided
|
147
|
+
if stdin_content is not None:
|
148
|
+
context["stdin"] = stdin_content
|
149
|
+
|
150
|
+
return context
|
151
|
+
|
152
|
+
|
100
153
|
class CLIParams(TypedDict, total=False):
|
101
154
|
"""Type-safe CLI parameters."""
|
102
155
|
|
@@ -185,12 +238,6 @@ ItemType: TypeAlias = Type[BaseModel]
|
|
185
238
|
ValueType: TypeAlias = Type[Any]
|
186
239
|
|
187
240
|
|
188
|
-
def is_container_type(tp: Type[Any]) -> bool:
|
189
|
-
"""Check if a type is a container type (list, dict, etc.)."""
|
190
|
-
origin = get_origin(tp)
|
191
|
-
return origin in (list, dict)
|
192
|
-
|
193
|
-
|
194
241
|
def _create_field(**kwargs: Any) -> FieldInfoType:
|
195
242
|
"""Create a Pydantic Field with the given kwargs."""
|
196
243
|
field: FieldInfoType = Field(**kwargs)
|
@@ -877,8 +924,11 @@ def collect_template_files(
|
|
877
924
|
# Let PathSecurityError propagate without wrapping
|
878
925
|
raise
|
879
926
|
except (FileNotFoundError, DirectoryNotFoundError) as e:
|
880
|
-
#
|
881
|
-
|
927
|
+
# Convert FileNotFoundError to OstructFileNotFoundError
|
928
|
+
if isinstance(e, FileNotFoundError):
|
929
|
+
raise OstructFileNotFoundError(str(e))
|
930
|
+
# Let DirectoryNotFoundError propagate
|
931
|
+
raise
|
882
932
|
except Exception as e:
|
883
933
|
# Don't wrap InvalidJSONError
|
884
934
|
if isinstance(e, InvalidJSONError):
|
@@ -980,38 +1030,6 @@ def collect_json_variables(args: CLIParams) -> Dict[str, Any]:
|
|
980
1030
|
return variables
|
981
1031
|
|
982
1032
|
|
983
|
-
def create_template_context(
|
984
|
-
files: Optional[
|
985
|
-
Dict[str, Union[FileInfoList, str, List[str], Dict[str, str]]]
|
986
|
-
] = None,
|
987
|
-
variables: Optional[Dict[str, str]] = None,
|
988
|
-
json_variables: Optional[Dict[str, Any]] = None,
|
989
|
-
security_manager: Optional[SecurityManager] = None,
|
990
|
-
stdin_content: Optional[str] = None,
|
991
|
-
) -> Dict[str, Any]:
|
992
|
-
"""Create template context from files and variables."""
|
993
|
-
context: Dict[str, Any] = {}
|
994
|
-
|
995
|
-
# Add file variables
|
996
|
-
if files:
|
997
|
-
for name, file_list in files.items():
|
998
|
-
context[name] = file_list # Always keep FileInfoList wrapper
|
999
|
-
|
1000
|
-
# Add simple variables
|
1001
|
-
if variables:
|
1002
|
-
context.update(variables)
|
1003
|
-
|
1004
|
-
# Add JSON variables
|
1005
|
-
if json_variables:
|
1006
|
-
context.update(json_variables)
|
1007
|
-
|
1008
|
-
# Add stdin if provided
|
1009
|
-
if stdin_content is not None:
|
1010
|
-
context["stdin"] = stdin_content
|
1011
|
-
|
1012
|
-
return context
|
1013
|
-
|
1014
|
-
|
1015
1033
|
async def create_template_context_from_args(
|
1016
1034
|
args: CLIParams,
|
1017
1035
|
security_manager: SecurityManager,
|
@@ -1066,8 +1084,11 @@ async def create_template_context_from_args(
|
|
1066
1084
|
# Let PathSecurityError propagate without wrapping
|
1067
1085
|
raise
|
1068
1086
|
except (FileNotFoundError, DirectoryNotFoundError) as e:
|
1069
|
-
#
|
1070
|
-
|
1087
|
+
# Convert FileNotFoundError to OstructFileNotFoundError
|
1088
|
+
if isinstance(e, FileNotFoundError):
|
1089
|
+
raise OstructFileNotFoundError(str(e))
|
1090
|
+
# Let DirectoryNotFoundError propagate
|
1091
|
+
raise
|
1071
1092
|
except Exception as e:
|
1072
1093
|
# Don't wrap InvalidJSONError
|
1073
1094
|
if isinstance(e, InvalidJSONError):
|
@@ -1197,41 +1218,6 @@ def parse_json_var(var_str: str) -> Tuple[str, Any]:
|
|
1197
1218
|
raise
|
1198
1219
|
|
1199
1220
|
|
1200
|
-
def _create_enum_type(values: List[Any], field_name: str) -> Type[Enum]:
|
1201
|
-
"""Create an enum type from a list of values.
|
1202
|
-
|
1203
|
-
Args:
|
1204
|
-
values: List of enum values
|
1205
|
-
field_name: Name of the field for enum type name
|
1206
|
-
|
1207
|
-
Returns:
|
1208
|
-
Created enum type
|
1209
|
-
"""
|
1210
|
-
# Determine the value type
|
1211
|
-
value_types = {type(v) for v in values}
|
1212
|
-
|
1213
|
-
if len(value_types) > 1:
|
1214
|
-
# Mixed types, use string representation
|
1215
|
-
enum_dict = {f"VALUE_{i}": str(v) for i, v in enumerate(values)}
|
1216
|
-
return type(f"{field_name.title()}Enum", (str, Enum), enum_dict)
|
1217
|
-
elif value_types == {int}:
|
1218
|
-
# All integer values
|
1219
|
-
enum_dict = {f"VALUE_{v}": v for v in values}
|
1220
|
-
return type(f"{field_name.title()}Enum", (IntEnum,), enum_dict)
|
1221
|
-
elif value_types == {str}:
|
1222
|
-
# All string values
|
1223
|
-
enum_dict = {v.upper().replace(" ", "_"): v for v in values}
|
1224
|
-
if sys.version_info >= (3, 11):
|
1225
|
-
return type(f"{field_name.title()}Enum", (StrEnum,), enum_dict)
|
1226
|
-
else:
|
1227
|
-
# Other types, use string representation
|
1228
|
-
return type(f"{field_name.title()}Enum", (str, Enum), enum_dict)
|
1229
|
-
|
1230
|
-
# Default case: treat as string enum
|
1231
|
-
enum_dict = {f"VALUE_{i}": str(v) for i, v in enumerate(values)}
|
1232
|
-
return type(f"{field_name.title()}Enum", (str, Enum), enum_dict)
|
1233
|
-
|
1234
|
-
|
1235
1221
|
def handle_error(e: Exception) -> None:
|
1236
1222
|
"""Handle CLI errors and display appropriate messages.
|
1237
1223
|
|
@@ -1433,7 +1419,7 @@ async def stream_structured_output(
|
|
1433
1419
|
EmptyResponseError,
|
1434
1420
|
InvalidResponseFormatError,
|
1435
1421
|
) as e:
|
1436
|
-
logger.error(
|
1422
|
+
logger.error("Stream error: %s", str(e))
|
1437
1423
|
raise
|
1438
1424
|
finally:
|
1439
1425
|
# Always ensure client is properly closed
|
@@ -1941,254 +1927,5 @@ __all__ = [
|
|
1941
1927
|
]
|
1942
1928
|
|
1943
1929
|
|
1944
|
-
def create_dynamic_model(
|
1945
|
-
schema: Dict[str, Any],
|
1946
|
-
base_name: str = "DynamicModel",
|
1947
|
-
show_schema: bool = False,
|
1948
|
-
debug_validation: bool = False,
|
1949
|
-
) -> Type[BaseModel]:
|
1950
|
-
"""Create a Pydantic model from a JSON schema.
|
1951
|
-
|
1952
|
-
Args:
|
1953
|
-
schema: JSON schema to create model from
|
1954
|
-
base_name: Name for the model class
|
1955
|
-
show_schema: Whether to show the generated model schema
|
1956
|
-
debug_validation: Whether to show detailed validation errors
|
1957
|
-
|
1958
|
-
Returns:
|
1959
|
-
Type[BaseModel]: The generated Pydantic model class
|
1960
|
-
|
1961
|
-
Raises:
|
1962
|
-
ModelValidationError: If the schema is invalid
|
1963
|
-
SchemaValidationError: If the schema violates OpenAI requirements
|
1964
|
-
"""
|
1965
|
-
if debug_validation:
|
1966
|
-
logger.info("Creating dynamic model from schema:")
|
1967
|
-
logger.info(json.dumps(schema, indent=2))
|
1968
|
-
|
1969
|
-
try:
|
1970
|
-
# Handle our wrapper format if present
|
1971
|
-
if "schema" in schema:
|
1972
|
-
if debug_validation:
|
1973
|
-
logger.info("Found schema wrapper, extracting inner schema")
|
1974
|
-
logger.info(
|
1975
|
-
"Original schema: %s", json.dumps(schema, indent=2)
|
1976
|
-
)
|
1977
|
-
inner_schema = schema["schema"]
|
1978
|
-
if not isinstance(inner_schema, dict):
|
1979
|
-
if debug_validation:
|
1980
|
-
logger.info(
|
1981
|
-
"Inner schema must be a dictionary, got %s",
|
1982
|
-
type(inner_schema),
|
1983
|
-
)
|
1984
|
-
raise SchemaValidationError(
|
1985
|
-
"Inner schema must be a dictionary"
|
1986
|
-
)
|
1987
|
-
if debug_validation:
|
1988
|
-
logger.info("Using inner schema:")
|
1989
|
-
logger.info(json.dumps(inner_schema, indent=2))
|
1990
|
-
schema = inner_schema
|
1991
|
-
|
1992
|
-
# Validate against OpenAI requirements
|
1993
|
-
from .schema_validation import validate_openai_schema
|
1994
|
-
|
1995
|
-
validate_openai_schema(schema)
|
1996
|
-
|
1997
|
-
# Create model configuration
|
1998
|
-
config = ConfigDict(
|
1999
|
-
title=schema.get("title", base_name),
|
2000
|
-
extra="forbid", # OpenAI requires additionalProperties: false
|
2001
|
-
validate_default=True,
|
2002
|
-
use_enum_values=True,
|
2003
|
-
arbitrary_types_allowed=True,
|
2004
|
-
json_schema_extra={
|
2005
|
-
k: v
|
2006
|
-
for k, v in schema.items()
|
2007
|
-
if k
|
2008
|
-
not in {
|
2009
|
-
"type",
|
2010
|
-
"properties",
|
2011
|
-
"required",
|
2012
|
-
"title",
|
2013
|
-
"description",
|
2014
|
-
"additionalProperties",
|
2015
|
-
"readOnly",
|
2016
|
-
}
|
2017
|
-
},
|
2018
|
-
)
|
2019
|
-
|
2020
|
-
if debug_validation:
|
2021
|
-
logger.info("Created model configuration:")
|
2022
|
-
logger.info(" Title: %s", config.get("title"))
|
2023
|
-
logger.info(" Extra: %s", config.get("extra"))
|
2024
|
-
logger.info(
|
2025
|
-
" Validate Default: %s", config.get("validate_default")
|
2026
|
-
)
|
2027
|
-
logger.info(" Use Enum Values: %s", config.get("use_enum_values"))
|
2028
|
-
logger.info(
|
2029
|
-
" Arbitrary Types: %s", config.get("arbitrary_types_allowed")
|
2030
|
-
)
|
2031
|
-
logger.info(
|
2032
|
-
" JSON Schema Extra: %s", config.get("json_schema_extra")
|
2033
|
-
)
|
2034
|
-
|
2035
|
-
# Process schema properties into fields
|
2036
|
-
properties = schema.get("properties", {})
|
2037
|
-
required = schema.get("required", [])
|
2038
|
-
|
2039
|
-
field_definitions: Dict[str, Tuple[Type[Any], FieldInfoType]] = {}
|
2040
|
-
for field_name, field_schema in properties.items():
|
2041
|
-
if debug_validation:
|
2042
|
-
logger.info("Processing field %s:", field_name)
|
2043
|
-
logger.info(" Schema: %s", json.dumps(field_schema, indent=2))
|
2044
|
-
|
2045
|
-
try:
|
2046
|
-
python_type, field = _get_type_with_constraints(
|
2047
|
-
field_schema, field_name, base_name
|
2048
|
-
)
|
2049
|
-
|
2050
|
-
# Handle optional fields
|
2051
|
-
if field_name not in required:
|
2052
|
-
if debug_validation:
|
2053
|
-
logger.info(
|
2054
|
-
"Field %s is optional, wrapping in Optional",
|
2055
|
-
field_name,
|
2056
|
-
)
|
2057
|
-
field_type = cast(Type[Any], Optional[python_type])
|
2058
|
-
else:
|
2059
|
-
field_type = python_type
|
2060
|
-
if debug_validation:
|
2061
|
-
logger.info("Field %s is required", field_name)
|
2062
|
-
|
2063
|
-
# Create field definition
|
2064
|
-
field_definitions[field_name] = (field_type, field)
|
2065
|
-
|
2066
|
-
if debug_validation:
|
2067
|
-
logger.info("Successfully created field definition:")
|
2068
|
-
logger.info(" Name: %s", field_name)
|
2069
|
-
logger.info(" Type: %s", str(field_type))
|
2070
|
-
logger.info(" Required: %s", field_name in required)
|
2071
|
-
|
2072
|
-
except (FieldDefinitionError, NestedModelError) as e:
|
2073
|
-
if debug_validation:
|
2074
|
-
logger.error("Error creating field %s:", field_name)
|
2075
|
-
logger.error(" Error type: %s", type(e).__name__)
|
2076
|
-
logger.error(" Error message: %s", str(e))
|
2077
|
-
raise ModelValidationError(base_name, [str(e)])
|
2078
|
-
|
2079
|
-
# Create the model with the fields
|
2080
|
-
field_defs: Dict[str, Any] = {
|
2081
|
-
name: (
|
2082
|
-
(
|
2083
|
-
cast(Type[Any], field_type)
|
2084
|
-
if is_container_type(field_type)
|
2085
|
-
else field_type
|
2086
|
-
),
|
2087
|
-
field,
|
2088
|
-
)
|
2089
|
-
for name, (field_type, field) in field_definitions.items()
|
2090
|
-
}
|
2091
|
-
model: Type[BaseModel] = create_model(
|
2092
|
-
base_name, __config__=config, **field_defs
|
2093
|
-
)
|
2094
|
-
|
2095
|
-
# Set the model config after creation
|
2096
|
-
model.model_config = config
|
2097
|
-
|
2098
|
-
if debug_validation:
|
2099
|
-
logger.info("Successfully created model: %s", model.__name__)
|
2100
|
-
logger.info("Model config: %s", dict(model.model_config))
|
2101
|
-
logger.info(
|
2102
|
-
"Model schema: %s",
|
2103
|
-
json.dumps(model.model_json_schema(), indent=2),
|
2104
|
-
)
|
2105
|
-
|
2106
|
-
# Validate the model's JSON schema
|
2107
|
-
try:
|
2108
|
-
model.model_json_schema()
|
2109
|
-
except ValidationError as e:
|
2110
|
-
validation_errors = (
|
2111
|
-
[str(err) for err in e.errors()]
|
2112
|
-
if hasattr(e, "errors")
|
2113
|
-
else [str(e)]
|
2114
|
-
)
|
2115
|
-
if debug_validation:
|
2116
|
-
logger.error("Schema validation failed:")
|
2117
|
-
logger.error(" Error type: %s", type(e).__name__)
|
2118
|
-
logger.error(" Error message: %s", str(e))
|
2119
|
-
raise ModelValidationError(base_name, validation_errors)
|
2120
|
-
|
2121
|
-
return model
|
2122
|
-
|
2123
|
-
except SchemaValidationError as e:
|
2124
|
-
# Always log basic error info
|
2125
|
-
logger.error("Schema validation error: %s", str(e))
|
2126
|
-
|
2127
|
-
# Log additional debug info if requested
|
2128
|
-
if debug_validation:
|
2129
|
-
logger.error(" Error type: %s", type(e).__name__)
|
2130
|
-
logger.error(" Error details: %s", str(e))
|
2131
|
-
# Always raise schema validation errors directly
|
2132
|
-
raise
|
2133
|
-
|
2134
|
-
except Exception as e:
|
2135
|
-
# Always log basic error info
|
2136
|
-
logger.error("Model creation error: %s", str(e))
|
2137
|
-
|
2138
|
-
# Log additional debug info if requested
|
2139
|
-
if debug_validation:
|
2140
|
-
logger.error(" Error type: %s", type(e).__name__)
|
2141
|
-
logger.error(" Error details: %s", str(e))
|
2142
|
-
if hasattr(e, "__cause__"):
|
2143
|
-
logger.error(" Caused by: %s", str(e.__cause__))
|
2144
|
-
if hasattr(e, "__context__"):
|
2145
|
-
logger.error(" Context: %s", str(e.__context__))
|
2146
|
-
if hasattr(e, "__traceback__"):
|
2147
|
-
import traceback
|
2148
|
-
|
2149
|
-
logger.error(
|
2150
|
-
" Traceback:\n%s",
|
2151
|
-
"".join(traceback.format_tb(e.__traceback__)),
|
2152
|
-
)
|
2153
|
-
# Always wrap other errors as ModelCreationError
|
2154
|
-
raise ModelCreationError(
|
2155
|
-
f"Failed to create model {base_name}",
|
2156
|
-
context={"error": str(e)},
|
2157
|
-
) from e
|
2158
|
-
|
2159
|
-
|
2160
|
-
# Validation functions
|
2161
|
-
def pattern(regex: str) -> Any:
|
2162
|
-
return constr(pattern=regex)
|
2163
|
-
|
2164
|
-
|
2165
|
-
def min_length(length: int) -> Any:
|
2166
|
-
return BeforeValidator(lambda v: v if len(str(v)) >= length else None)
|
2167
|
-
|
2168
|
-
|
2169
|
-
def max_length(length: int) -> Any:
|
2170
|
-
return BeforeValidator(lambda v: v if len(str(v)) <= length else None)
|
2171
|
-
|
2172
|
-
|
2173
|
-
def ge(value: Union[int, float]) -> Any:
|
2174
|
-
return BeforeValidator(lambda v: v if float(v) >= value else None)
|
2175
|
-
|
2176
|
-
|
2177
|
-
def le(value: Union[int, float]) -> Any:
|
2178
|
-
return BeforeValidator(lambda v: v if float(v) <= value else None)
|
2179
|
-
|
2180
|
-
|
2181
|
-
def gt(value: Union[int, float]) -> Any:
|
2182
|
-
return BeforeValidator(lambda v: v if float(v) > value else None)
|
2183
|
-
|
2184
|
-
|
2185
|
-
def lt(value: Union[int, float]) -> Any:
|
2186
|
-
return BeforeValidator(lambda v: v if float(v) < value else None)
|
2187
|
-
|
2188
|
-
|
2189
|
-
def multiple_of(value: Union[int, float]) -> Any:
|
2190
|
-
return BeforeValidator(lambda v: v if float(v) % value == 0 else None)
|
2191
|
-
|
2192
|
-
|
2193
1930
|
if __name__ == "__main__":
|
2194
1931
|
main()
|
@@ -0,0 +1,507 @@
|
|
1
|
+
"""Model creation utilities for the CLI."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
import sys
|
6
|
+
from datetime import date, datetime, time
|
7
|
+
from enum import Enum, IntEnum
|
8
|
+
from typing import (
|
9
|
+
Any,
|
10
|
+
Dict,
|
11
|
+
List,
|
12
|
+
Optional,
|
13
|
+
Tuple,
|
14
|
+
Type,
|
15
|
+
Union,
|
16
|
+
cast,
|
17
|
+
get_origin,
|
18
|
+
)
|
19
|
+
|
20
|
+
if sys.version_info >= (3, 11):
|
21
|
+
from enum import StrEnum
|
22
|
+
|
23
|
+
from pydantic import (
|
24
|
+
AnyUrl,
|
25
|
+
BaseModel,
|
26
|
+
ConfigDict,
|
27
|
+
EmailStr,
|
28
|
+
Field,
|
29
|
+
ValidationError,
|
30
|
+
create_model,
|
31
|
+
)
|
32
|
+
from pydantic.fields import FieldInfo
|
33
|
+
from pydantic.functional_validators import BeforeValidator
|
34
|
+
from pydantic.types import constr
|
35
|
+
|
36
|
+
from .errors import (
|
37
|
+
FieldDefinitionError,
|
38
|
+
ModelCreationError,
|
39
|
+
ModelValidationError,
|
40
|
+
NestedModelError,
|
41
|
+
SchemaValidationError,
|
42
|
+
)
|
43
|
+
|
44
|
+
logger = logging.getLogger(__name__)
|
45
|
+
|
46
|
+
# Type aliases
|
47
|
+
FieldType = Type[
|
48
|
+
Any
|
49
|
+
] # Changed from Type[Any] to allow both concrete types and generics
|
50
|
+
FieldDefinition = Tuple[Any, FieldInfo] # Changed to Any to handle generics
|
51
|
+
|
52
|
+
|
53
|
+
def _create_enum_type(values: List[Any], field_name: str) -> Type[Enum]:
|
54
|
+
"""Create an enum type from a list of values.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
values: List of enum values
|
58
|
+
field_name: Name of the field for enum type name
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
Created enum type
|
62
|
+
"""
|
63
|
+
# Determine the value type
|
64
|
+
value_types = {type(v) for v in values}
|
65
|
+
|
66
|
+
if len(value_types) > 1:
|
67
|
+
# Mixed types, use string representation
|
68
|
+
enum_dict = {f"VALUE_{i}": str(v) for i, v in enumerate(values)}
|
69
|
+
return type(f"{field_name.title()}Enum", (str, Enum), enum_dict)
|
70
|
+
elif value_types == {int}:
|
71
|
+
# All integer values
|
72
|
+
enum_dict = {f"VALUE_{v}": v for v in values}
|
73
|
+
return type(f"{field_name.title()}Enum", (IntEnum,), enum_dict)
|
74
|
+
elif value_types == {str}:
|
75
|
+
# All string values
|
76
|
+
enum_dict = {v.upper().replace(" ", "_"): v for v in values}
|
77
|
+
if sys.version_info >= (3, 11):
|
78
|
+
return type(f"{field_name.title()}Enum", (StrEnum,), enum_dict)
|
79
|
+
else:
|
80
|
+
# Other types, use string representation
|
81
|
+
return type(f"{field_name.title()}Enum", (str, Enum), enum_dict)
|
82
|
+
|
83
|
+
# Default case: treat as string enum
|
84
|
+
enum_dict = {f"VALUE_{i}": str(v) for i, v in enumerate(values)}
|
85
|
+
return type(f"{field_name.title()}Enum", (str, Enum), enum_dict)
|
86
|
+
|
87
|
+
|
88
|
+
def is_container_type(tp: Type[Any]) -> bool:
|
89
|
+
"""Check if a type is a container type (List, Dict, etc).
|
90
|
+
|
91
|
+
Args:
|
92
|
+
tp: Type to check
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
bool: True if type is a container type
|
96
|
+
"""
|
97
|
+
origin = get_origin(tp)
|
98
|
+
return origin is not None and origin in (list, dict, List, Dict)
|
99
|
+
|
100
|
+
|
101
|
+
# Validation functions
|
102
|
+
def pattern(regex: str) -> Any:
|
103
|
+
return constr(pattern=regex)
|
104
|
+
|
105
|
+
|
106
|
+
def min_length(length: int) -> Any:
|
107
|
+
return BeforeValidator(lambda v: v if len(str(v)) >= length else None)
|
108
|
+
|
109
|
+
|
110
|
+
def max_length(length: int) -> Any:
|
111
|
+
return BeforeValidator(lambda v: v if len(str(v)) <= length else None)
|
112
|
+
|
113
|
+
|
114
|
+
def ge(value: Union[int, float]) -> Any:
|
115
|
+
return BeforeValidator(lambda v: v if float(v) >= value else None)
|
116
|
+
|
117
|
+
|
118
|
+
def le(value: Union[int, float]) -> Any:
|
119
|
+
return BeforeValidator(lambda v: v if float(v) <= value else None)
|
120
|
+
|
121
|
+
|
122
|
+
def gt(value: Union[int, float]) -> Any:
|
123
|
+
return BeforeValidator(lambda v: v if float(v) > value else None)
|
124
|
+
|
125
|
+
|
126
|
+
def lt(value: Union[int, float]) -> Any:
|
127
|
+
return BeforeValidator(lambda v: v if float(v) < value else None)
|
128
|
+
|
129
|
+
|
130
|
+
def multiple_of(value: Union[int, float]) -> Any:
|
131
|
+
return BeforeValidator(lambda v: v if float(v) % value == 0 else None)
|
132
|
+
|
133
|
+
|
134
|
+
def _get_type_with_constraints(
|
135
|
+
field_schema: Dict[str, Any], field_name: str, base_name: str
|
136
|
+
) -> FieldDefinition:
|
137
|
+
"""Get type with constraints from field schema.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
field_schema: Field schema dict
|
141
|
+
field_name: Name of the field
|
142
|
+
base_name: Base name for nested models
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Tuple of (type, field)
|
146
|
+
"""
|
147
|
+
field_kwargs: Dict[str, Any] = {}
|
148
|
+
|
149
|
+
# Add common field metadata
|
150
|
+
if "title" in field_schema:
|
151
|
+
field_kwargs["title"] = field_schema["title"]
|
152
|
+
if "description" in field_schema:
|
153
|
+
field_kwargs["description"] = field_schema["description"]
|
154
|
+
if "default" in field_schema:
|
155
|
+
field_kwargs["default"] = field_schema["default"]
|
156
|
+
if "readOnly" in field_schema:
|
157
|
+
field_kwargs["frozen"] = field_schema["readOnly"]
|
158
|
+
|
159
|
+
field_type = field_schema.get("type")
|
160
|
+
|
161
|
+
# Handle array type
|
162
|
+
if field_type == "array":
|
163
|
+
items_schema = field_schema.get("items", {})
|
164
|
+
if not items_schema:
|
165
|
+
return (List[Any], Field(**field_kwargs)) # Direct generic type
|
166
|
+
|
167
|
+
# Create nested model for object items
|
168
|
+
if (
|
169
|
+
isinstance(items_schema, dict)
|
170
|
+
and items_schema.get("type") == "object"
|
171
|
+
):
|
172
|
+
array_item_model = create_dynamic_model(
|
173
|
+
items_schema,
|
174
|
+
base_name=f"{base_name}_{field_name}_Item",
|
175
|
+
show_schema=False,
|
176
|
+
debug_validation=False,
|
177
|
+
)
|
178
|
+
return (List[array_item_model], Field(**field_kwargs)) # type: ignore[valid-type]
|
179
|
+
|
180
|
+
# For non-object items, use the type directly
|
181
|
+
item_type = items_schema.get("type", "string")
|
182
|
+
if item_type == "string":
|
183
|
+
return (List[str], Field(**field_kwargs))
|
184
|
+
elif item_type == "integer":
|
185
|
+
return (List[int], Field(**field_kwargs))
|
186
|
+
elif item_type == "number":
|
187
|
+
return (List[float], Field(**field_kwargs))
|
188
|
+
elif item_type == "boolean":
|
189
|
+
return (List[bool], Field(**field_kwargs))
|
190
|
+
else:
|
191
|
+
return (List[Any], Field(**field_kwargs))
|
192
|
+
|
193
|
+
# Handle object type
|
194
|
+
if field_type == "object":
|
195
|
+
# Create nested model with explicit type annotation
|
196
|
+
object_model = create_dynamic_model(
|
197
|
+
field_schema,
|
198
|
+
base_name=f"{base_name}_{field_name}",
|
199
|
+
show_schema=False,
|
200
|
+
debug_validation=False,
|
201
|
+
)
|
202
|
+
return (object_model, Field(**field_kwargs))
|
203
|
+
|
204
|
+
# Handle additionalProperties
|
205
|
+
if "additionalProperties" in field_schema and isinstance(
|
206
|
+
field_schema["additionalProperties"], dict
|
207
|
+
):
|
208
|
+
# Create nested model with explicit type annotation
|
209
|
+
dict_value_model = create_dynamic_model(
|
210
|
+
field_schema["additionalProperties"],
|
211
|
+
base_name=f"{base_name}_{field_name}_Value",
|
212
|
+
show_schema=False,
|
213
|
+
debug_validation=False,
|
214
|
+
)
|
215
|
+
dict_type: Type[Dict[str, Any]] = Dict[str, dict_value_model] # type: ignore[valid-type]
|
216
|
+
return (dict_type, Field(**field_kwargs))
|
217
|
+
|
218
|
+
# Handle other types
|
219
|
+
if field_type == "string":
|
220
|
+
field_type_cls: Type[Any] = str
|
221
|
+
|
222
|
+
# Add string-specific constraints to field_kwargs
|
223
|
+
if "pattern" in field_schema:
|
224
|
+
field_kwargs["pattern"] = field_schema["pattern"]
|
225
|
+
if "minLength" in field_schema:
|
226
|
+
field_kwargs["min_length"] = field_schema["minLength"]
|
227
|
+
if "maxLength" in field_schema:
|
228
|
+
field_kwargs["max_length"] = field_schema["maxLength"]
|
229
|
+
|
230
|
+
# Handle special string formats
|
231
|
+
if "format" in field_schema:
|
232
|
+
if field_schema["format"] == "date-time":
|
233
|
+
field_type_cls = datetime
|
234
|
+
elif field_schema["format"] == "date":
|
235
|
+
field_type_cls = date
|
236
|
+
elif field_schema["format"] == "time":
|
237
|
+
field_type_cls = time
|
238
|
+
elif field_schema["format"] == "email":
|
239
|
+
field_type_cls = EmailStr
|
240
|
+
elif field_schema["format"] == "uri":
|
241
|
+
field_type_cls = AnyUrl
|
242
|
+
|
243
|
+
return (field_type_cls, Field(**field_kwargs))
|
244
|
+
|
245
|
+
if field_type == "number":
|
246
|
+
field_type_cls = float
|
247
|
+
|
248
|
+
# Add number-specific constraints to field_kwargs
|
249
|
+
if "minimum" in field_schema:
|
250
|
+
field_kwargs["ge"] = field_schema["minimum"]
|
251
|
+
if "maximum" in field_schema:
|
252
|
+
field_kwargs["le"] = field_schema["maximum"]
|
253
|
+
if "exclusiveMinimum" in field_schema:
|
254
|
+
field_kwargs["gt"] = field_schema["exclusiveMinimum"]
|
255
|
+
if "exclusiveMaximum" in field_schema:
|
256
|
+
field_kwargs["lt"] = field_schema["exclusiveMaximum"]
|
257
|
+
if "multipleOf" in field_schema:
|
258
|
+
field_kwargs["multiple_of"] = field_schema["multipleOf"]
|
259
|
+
|
260
|
+
return (field_type_cls, Field(**field_kwargs))
|
261
|
+
|
262
|
+
if field_type == "integer":
|
263
|
+
field_type_cls = int
|
264
|
+
|
265
|
+
# Add integer-specific constraints to field_kwargs
|
266
|
+
if "minimum" in field_schema:
|
267
|
+
field_kwargs["ge"] = field_schema["minimum"]
|
268
|
+
if "maximum" in field_schema:
|
269
|
+
field_kwargs["le"] = field_schema["maximum"]
|
270
|
+
if "exclusiveMinimum" in field_schema:
|
271
|
+
field_kwargs["gt"] = field_schema["exclusiveMinimum"]
|
272
|
+
if "exclusiveMaximum" in field_schema:
|
273
|
+
field_kwargs["lt"] = field_schema["exclusiveMaximum"]
|
274
|
+
if "multipleOf" in field_schema:
|
275
|
+
field_kwargs["multiple_of"] = field_schema["multipleOf"]
|
276
|
+
|
277
|
+
return (field_type_cls, Field(**field_kwargs))
|
278
|
+
|
279
|
+
if field_type == "boolean":
|
280
|
+
return (bool, Field(**field_kwargs))
|
281
|
+
|
282
|
+
if field_type == "null":
|
283
|
+
return (type(None), Field(**field_kwargs))
|
284
|
+
|
285
|
+
# Handle enum
|
286
|
+
if "enum" in field_schema:
|
287
|
+
enum_type = _create_enum_type(field_schema["enum"], field_name)
|
288
|
+
return (cast(Type[Any], enum_type), Field(**field_kwargs))
|
289
|
+
|
290
|
+
# Default to Any for unknown types
|
291
|
+
return (Any, Field(**field_kwargs))
|
292
|
+
|
293
|
+
|
294
|
+
def create_dynamic_model(
|
295
|
+
schema: Dict[str, Any],
|
296
|
+
base_name: str = "DynamicModel",
|
297
|
+
show_schema: bool = False,
|
298
|
+
debug_validation: bool = False,
|
299
|
+
) -> Type[BaseModel]:
|
300
|
+
"""Create a Pydantic model from a JSON schema.
|
301
|
+
|
302
|
+
Args:
|
303
|
+
schema: JSON schema to create model from
|
304
|
+
base_name: Name for the model class
|
305
|
+
show_schema: Whether to show the generated model schema
|
306
|
+
debug_validation: Whether to show detailed validation errors
|
307
|
+
|
308
|
+
Returns:
|
309
|
+
Type[BaseModel]: The generated Pydantic model class
|
310
|
+
|
311
|
+
Raises:
|
312
|
+
ModelValidationError: If the schema is invalid
|
313
|
+
SchemaValidationError: If the schema violates OpenAI requirements
|
314
|
+
"""
|
315
|
+
if debug_validation:
|
316
|
+
logger.info("Creating dynamic model from schema:")
|
317
|
+
logger.info(json.dumps(schema, indent=2))
|
318
|
+
|
319
|
+
try:
|
320
|
+
# Handle our wrapper format if present
|
321
|
+
if "schema" in schema:
|
322
|
+
if debug_validation:
|
323
|
+
logger.info("Found schema wrapper, extracting inner schema")
|
324
|
+
logger.info(
|
325
|
+
"Original schema: %s", json.dumps(schema, indent=2)
|
326
|
+
)
|
327
|
+
inner_schema = schema["schema"]
|
328
|
+
if not isinstance(inner_schema, dict):
|
329
|
+
if debug_validation:
|
330
|
+
logger.info(
|
331
|
+
"Inner schema must be a dictionary, got %s",
|
332
|
+
type(inner_schema),
|
333
|
+
)
|
334
|
+
raise SchemaValidationError(
|
335
|
+
"Inner schema must be a dictionary"
|
336
|
+
)
|
337
|
+
if debug_validation:
|
338
|
+
logger.info("Using inner schema:")
|
339
|
+
logger.info(json.dumps(inner_schema, indent=2))
|
340
|
+
schema = inner_schema
|
341
|
+
|
342
|
+
# Validate against OpenAI requirements
|
343
|
+
from .schema_validation import validate_openai_schema
|
344
|
+
|
345
|
+
validate_openai_schema(schema)
|
346
|
+
|
347
|
+
# Create model configuration
|
348
|
+
config = ConfigDict(
|
349
|
+
title=schema.get("title", base_name),
|
350
|
+
extra="forbid", # OpenAI requires additionalProperties: false
|
351
|
+
validate_default=True,
|
352
|
+
use_enum_values=True,
|
353
|
+
arbitrary_types_allowed=True,
|
354
|
+
json_schema_extra={
|
355
|
+
k: v
|
356
|
+
for k, v in schema.items()
|
357
|
+
if k
|
358
|
+
not in {
|
359
|
+
"type",
|
360
|
+
"properties",
|
361
|
+
"required",
|
362
|
+
"title",
|
363
|
+
"description",
|
364
|
+
"additionalProperties",
|
365
|
+
"readOnly",
|
366
|
+
}
|
367
|
+
},
|
368
|
+
)
|
369
|
+
|
370
|
+
if debug_validation:
|
371
|
+
logger.info("Created model configuration:")
|
372
|
+
logger.info(" Title: %s", config.get("title"))
|
373
|
+
logger.info(" Extra: %s", config.get("extra"))
|
374
|
+
logger.info(
|
375
|
+
" Validate Default: %s", config.get("validate_default")
|
376
|
+
)
|
377
|
+
logger.info(" Use Enum Values: %s", config.get("use_enum_values"))
|
378
|
+
logger.info(
|
379
|
+
" Arbitrary Types: %s", config.get("arbitrary_types_allowed")
|
380
|
+
)
|
381
|
+
logger.info(
|
382
|
+
" JSON Schema Extra: %s", config.get("json_schema_extra")
|
383
|
+
)
|
384
|
+
|
385
|
+
# Process schema properties into fields
|
386
|
+
properties = schema.get("properties", {})
|
387
|
+
required = schema.get("required", [])
|
388
|
+
|
389
|
+
field_definitions: Dict[str, Tuple[Type[Any], FieldInfo]] = {}
|
390
|
+
for field_name, field_schema in properties.items():
|
391
|
+
if debug_validation:
|
392
|
+
logger.info("Processing field %s:", field_name)
|
393
|
+
logger.info(" Schema: %s", json.dumps(field_schema, indent=2))
|
394
|
+
|
395
|
+
try:
|
396
|
+
python_type, field = _get_type_with_constraints(
|
397
|
+
field_schema, field_name, base_name
|
398
|
+
)
|
399
|
+
|
400
|
+
# Handle optional fields
|
401
|
+
if field_name not in required:
|
402
|
+
if debug_validation:
|
403
|
+
logger.info(
|
404
|
+
"Field %s is optional, wrapping in Optional",
|
405
|
+
field_name,
|
406
|
+
)
|
407
|
+
field_type = cast(Type[Any], Optional[python_type])
|
408
|
+
else:
|
409
|
+
field_type = python_type
|
410
|
+
if debug_validation:
|
411
|
+
logger.info("Field %s is required", field_name)
|
412
|
+
|
413
|
+
# Create field definition
|
414
|
+
field_definitions[field_name] = (field_type, field)
|
415
|
+
|
416
|
+
if debug_validation:
|
417
|
+
logger.info("Successfully created field definition:")
|
418
|
+
logger.info(" Name: %s", field_name)
|
419
|
+
logger.info(" Type: %s", str(field_type))
|
420
|
+
logger.info(" Required: %s", field_name in required)
|
421
|
+
|
422
|
+
except (FieldDefinitionError, NestedModelError) as e:
|
423
|
+
if debug_validation:
|
424
|
+
logger.error("Error creating field %s:", field_name)
|
425
|
+
logger.error(" Error type: %s", type(e).__name__)
|
426
|
+
logger.error(" Error message: %s", str(e))
|
427
|
+
raise ModelValidationError(base_name, [str(e)])
|
428
|
+
|
429
|
+
# Create the model with the fields
|
430
|
+
field_defs: Dict[str, Any] = {
|
431
|
+
name: (
|
432
|
+
(
|
433
|
+
cast(Type[Any], field_type)
|
434
|
+
if is_container_type(field_type)
|
435
|
+
else field_type
|
436
|
+
),
|
437
|
+
field,
|
438
|
+
)
|
439
|
+
for name, (field_type, field) in field_definitions.items()
|
440
|
+
}
|
441
|
+
model: Type[BaseModel] = create_model(
|
442
|
+
base_name, __config__=config, **field_defs
|
443
|
+
)
|
444
|
+
|
445
|
+
# Set the model config after creation
|
446
|
+
model.model_config = config
|
447
|
+
|
448
|
+
if debug_validation:
|
449
|
+
logger.info("Successfully created model: %s", model.__name__)
|
450
|
+
logger.info("Model config: %s", dict(model.model_config))
|
451
|
+
logger.info(
|
452
|
+
"Model schema: %s",
|
453
|
+
json.dumps(model.model_json_schema(), indent=2),
|
454
|
+
)
|
455
|
+
|
456
|
+
# Validate the model's JSON schema
|
457
|
+
try:
|
458
|
+
model.model_json_schema()
|
459
|
+
except ValidationError as e:
|
460
|
+
validation_errors = (
|
461
|
+
[str(err) for err in e.errors()]
|
462
|
+
if hasattr(e, "errors")
|
463
|
+
else [str(e)]
|
464
|
+
)
|
465
|
+
if debug_validation:
|
466
|
+
logger.error("Schema validation failed:")
|
467
|
+
logger.error(" Error type: %s", type(e).__name__)
|
468
|
+
logger.error(" Error message: %s", str(e))
|
469
|
+
raise ModelValidationError(base_name, validation_errors)
|
470
|
+
|
471
|
+
return model
|
472
|
+
|
473
|
+
except SchemaValidationError as e:
|
474
|
+
# Always log basic error info
|
475
|
+
logger.error("Schema validation error: %s", str(e))
|
476
|
+
|
477
|
+
# Log additional debug info if requested
|
478
|
+
if debug_validation:
|
479
|
+
logger.error(" Error type: %s", type(e).__name__)
|
480
|
+
logger.error(" Error details: %s", str(e))
|
481
|
+
# Always raise schema validation errors directly
|
482
|
+
raise
|
483
|
+
|
484
|
+
except Exception as e:
|
485
|
+
# Always log basic error info
|
486
|
+
logger.error("Model creation error: %s", str(e))
|
487
|
+
|
488
|
+
# Log additional debug info if requested
|
489
|
+
if debug_validation:
|
490
|
+
logger.error(" Error type: %s", type(e).__name__)
|
491
|
+
logger.error(" Error details: %s", str(e))
|
492
|
+
if hasattr(e, "__cause__"):
|
493
|
+
logger.error(" Caused by: %s", str(e.__cause__))
|
494
|
+
if hasattr(e, "__context__"):
|
495
|
+
logger.error(" Context: %s", str(e.__context__))
|
496
|
+
if hasattr(e, "__traceback__"):
|
497
|
+
import traceback
|
498
|
+
|
499
|
+
logger.error(
|
500
|
+
" Traceback:\n%s",
|
501
|
+
"".join(traceback.format_tb(e.__traceback__)),
|
502
|
+
)
|
503
|
+
# Always wrap other errors as ModelCreationError
|
504
|
+
raise ModelCreationError(
|
505
|
+
f"Failed to create model {base_name}",
|
506
|
+
context={"error": str(e)},
|
507
|
+
) from e
|
@@ -2,13 +2,14 @@ ostruct/__init__.py,sha256=X6zo6V7ZNMv731Wi388aTVQngD1410ExGwGx4J6lpyo,187
|
|
2
2
|
ostruct/cli/__init__.py,sha256=sYHKT6o1kFy1acbXejzAvVm8Cy8U91Yf1l4DlzquHKg,409
|
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=lagB4j8G1hg2NmAYvWEarA24qYuY2w-cuRWiqUzoWik,65105
|
6
6
|
ostruct/cli/click_options.py,sha256=WbRJdB9sO63ChN3fnCP7XWs73DHKl0C1ervfwL11am0,11371
|
7
7
|
ostruct/cli/errors.py,sha256=zJdJ-AyzjCE8glVKbJGAcB-Mz1J1SlzTDJDmhqAVFYc,14930
|
8
8
|
ostruct/cli/exit_codes.py,sha256=uNjvQeUGwU1mlUJYIDrExAn7YlwOXZo603yLAwpqIwk,338
|
9
9
|
ostruct/cli/file_info.py,sha256=ilpT8IuckfhadLF1QQAPLXJp7p8kVpffDEEJ2erHPZU,14485
|
10
10
|
ostruct/cli/file_list.py,sha256=jLuCd1ardoAXX8FNwPgIqEM-ixzr1xP5ZSqXo2lmrj0,11270
|
11
11
|
ostruct/cli/file_utils.py,sha256=J3-6fbEGQ7KD_bU81pAxueHLv9XV0X7f8FSMt_0AJGQ,22537
|
12
|
+
ostruct/cli/model_creation.py,sha256=TmqJVdnZOYtTctNihOlxWIbyAfX-zfxehP9rp2t6P2c,17586
|
12
13
|
ostruct/cli/path_utils.py,sha256=j44q1OoLkqMErgK-qEuhuIZ1VyzqRIvNgxR1et9PoXA,4813
|
13
14
|
ostruct/cli/progress.py,sha256=rj9nVEco5UeZORMbzd7mFJpFGJjbH9KbBFh5oTE5Anw,3415
|
14
15
|
ostruct/cli/schema_validation.py,sha256=ohEuxJ0KF93qphj0JSZDnrxDn0C2ZU37g-U2JY03onM,8154
|
@@ -36,8 +37,8 @@ ostruct/cli/token_utils.py,sha256=r4KPEO3Sec18Q6mU0aClK6XGShvusgUggXEQgEPPlaA,13
|
|
36
37
|
ostruct/cli/utils.py,sha256=1UCl4rHjBWKR5EKugvlVGHiHjO3XXmqvkgeAUSyIPDU,831
|
37
38
|
ostruct/cli/validators.py,sha256=BYFZeebCPZObTUjO1TaAMpsD6h7ROkYAFn9C7uf1Q68,2992
|
38
39
|
ostruct/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
|
-
ostruct_cli-0.6.
|
40
|
-
ostruct_cli-0.6.
|
41
|
-
ostruct_cli-0.6.
|
42
|
-
ostruct_cli-0.6.
|
43
|
-
ostruct_cli-0.6.
|
40
|
+
ostruct_cli-0.6.1.dist-info/LICENSE,sha256=QUOY6QCYVxAiH8vdrUTDqe3i9hQ5bcNczppDSVpLTjk,1068
|
41
|
+
ostruct_cli-0.6.1.dist-info/METADATA,sha256=2D0_QCNb3xN2Y_K1pMB5WmZBcU8KkN2rqS9qwZMa-pc,10426
|
42
|
+
ostruct_cli-0.6.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
43
|
+
ostruct_cli-0.6.1.dist-info/entry_points.txt,sha256=NFq9IuqHVTem0j9zKjV8C1si_zGcP1RL6Wbvt9fUDXw,48
|
44
|
+
ostruct_cli-0.6.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|