chatterer 0.1.14__py3-none-any.whl → 0.1.17__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.
- chatterer/__init__.py +0 -4
- chatterer/interactive.py +42 -41
- chatterer/language_model.py +108 -29
- chatterer/utils/__init__.py +0 -3
- {chatterer-0.1.14.dist-info → chatterer-0.1.17.dist-info}/METADATA +6 -1
- {chatterer-0.1.14.dist-info → chatterer-0.1.17.dist-info}/RECORD +8 -9
- {chatterer-0.1.14.dist-info → chatterer-0.1.17.dist-info}/WHEEL +1 -1
- chatterer/utils/cli.py +0 -476
- {chatterer-0.1.14.dist-info → chatterer-0.1.17.dist-info}/top_level.txt +0 -0
chatterer/__init__.py
CHANGED
@@ -41,9 +41,7 @@ from .tools import (
|
|
41
41
|
render_pdf_as_image,
|
42
42
|
)
|
43
43
|
from .utils import (
|
44
|
-
ArgumentSpec,
|
45
44
|
Base64Image,
|
46
|
-
BaseArguments,
|
47
45
|
CodeExecutionResult,
|
48
46
|
FunctionSignature,
|
49
47
|
get_default_repl_tool,
|
@@ -92,6 +90,4 @@ __all__ = [
|
|
92
90
|
"extract_text_from_pdf",
|
93
91
|
"open_pdf",
|
94
92
|
"render_pdf_as_image",
|
95
|
-
"ArgumentSpec",
|
96
|
-
"BaseArguments",
|
97
93
|
]
|
chatterer/interactive.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
import
|
2
|
-
from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional
|
1
|
+
from functools import cached_property
|
2
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Type, TypeVar, cast, overload
|
3
3
|
|
4
4
|
from langchain_core.messages import (
|
5
5
|
AIMessage,
|
@@ -9,8 +9,11 @@ from langchain_core.messages import (
|
|
9
9
|
)
|
10
10
|
from langchain_core.runnables import RunnableConfig
|
11
11
|
from pydantic import BaseModel, Field
|
12
|
+
from rich.console import Console
|
13
|
+
from rich.panel import Panel
|
14
|
+
from rich.prompt import Prompt
|
12
15
|
|
13
|
-
from .language_model import Chatterer
|
16
|
+
from .language_model import Chatterer, LanguageModelInput
|
14
17
|
from .utils.code_agent import (
|
15
18
|
DEFAULT_CODE_GENERATION_PROMPT,
|
16
19
|
DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT,
|
@@ -25,6 +28,7 @@ if TYPE_CHECKING:
|
|
25
28
|
# Import only for type hinting to avoid circular dependencies if necessary
|
26
29
|
from langchain_experimental.tools.python.tool import PythonAstREPLTool
|
27
30
|
|
31
|
+
T = TypeVar("T", bound=BaseModel)
|
28
32
|
|
29
33
|
# --- Pydantic Models ---
|
30
34
|
|
@@ -94,9 +98,8 @@ class Think(BaseModel):
|
|
94
98
|
|
95
99
|
# --- Interactive Shell Function ---
|
96
100
|
|
97
|
-
|
98
101
|
def interactive_shell(
|
99
|
-
chatterer: Chatterer
|
102
|
+
chatterer: Chatterer,
|
100
103
|
system_instruction: BaseMessage | Iterable[BaseMessage] = ([
|
101
104
|
SystemMessage(
|
102
105
|
"You are an AI assistant capable of answering questions and executing Python code to help users solve tasks."
|
@@ -112,10 +115,6 @@ def interactive_shell(
|
|
112
115
|
**kwargs: Any,
|
113
116
|
) -> None:
|
114
117
|
try:
|
115
|
-
from rich.console import Console
|
116
|
-
from rich.panel import Panel
|
117
|
-
from rich.prompt import Prompt
|
118
|
-
|
119
118
|
console = Console()
|
120
119
|
# Style settings
|
121
120
|
AI_STYLE = "bold bright_blue"
|
@@ -125,23 +124,28 @@ def interactive_shell(
|
|
125
124
|
except ImportError:
|
126
125
|
raise ImportError("Rich library not found. Please install it: pip install rich")
|
127
126
|
|
127
|
+
# --- Shell Initialization and Main Loop ---
|
128
|
+
if repl_tool is None:
|
129
|
+
repl_tool = get_default_repl_tool()
|
130
|
+
|
131
|
+
def set_locals(**kwargs: object) -> None:
|
132
|
+
"""Set local variables for the REPL tool."""
|
133
|
+
if repl_tool.locals is None: # pyright: ignore[reportUnknownMemberType]
|
134
|
+
repl_tool.locals = {}
|
135
|
+
for key, value in kwargs.items():
|
136
|
+
repl_tool.locals[key] = value # pyright: ignore[reportUnknownMemberType]
|
137
|
+
|
128
138
|
def respond(messages: list[BaseMessage]) -> str:
|
129
139
|
response = ""
|
130
|
-
|
140
|
+
with console.status("[bold yellow]AI is thinking..."):
|
141
|
+
response_panel = Panel("", title="AI Response", style=AI_STYLE, border_style="blue")
|
142
|
+
current_content = ""
|
131
143
|
for chunk in chatterer.generate_stream(messages=messages):
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
response_panel = Panel("", title="AI Response", style=AI_STYLE, border_style="blue")
|
138
|
-
current_content = ""
|
139
|
-
for chunk in chatterer.generate_stream(messages=messages):
|
140
|
-
current_content += chunk
|
141
|
-
# Update renderable (might not display smoothly without Live)
|
142
|
-
response_panel.renderable = current_content
|
143
|
-
response = current_content
|
144
|
-
console.print(Panel(response, title="AI Response", style=AI_STYLE))
|
144
|
+
current_content += chunk
|
145
|
+
# Update renderable (might not display smoothly without Live)
|
146
|
+
response_panel.renderable = current_content
|
147
|
+
response = current_content
|
148
|
+
console.print(Panel(response, title="AI Response", style=AI_STYLE))
|
145
149
|
return response.strip()
|
146
150
|
|
147
151
|
def complete_task(think_before_speak: ThinkBeforeSpeak) -> None:
|
@@ -159,15 +163,15 @@ def interactive_shell(
|
|
159
163
|
|
160
164
|
while True:
|
161
165
|
current_context = context + session_messages
|
162
|
-
is_tool_call_needed: IsToolCallNeeded = chatterer
|
163
|
-
|
164
|
-
messages=augment_prompt_for_toolcall(
|
166
|
+
is_tool_call_needed: IsToolCallNeeded = chatterer(
|
167
|
+
augment_prompt_for_toolcall(
|
165
168
|
function_signatures=function_signatures,
|
166
169
|
messages=current_context,
|
167
170
|
prompt_for_code_invoke=prompt_for_code_invoke,
|
168
171
|
function_reference_prefix=function_reference_prefix,
|
169
172
|
function_reference_seperator=function_reference_seperator,
|
170
173
|
),
|
174
|
+
IsToolCallNeeded,
|
171
175
|
config=config,
|
172
176
|
stop=stop,
|
173
177
|
**kwargs,
|
@@ -175,7 +179,8 @@ def interactive_shell(
|
|
175
179
|
|
176
180
|
if is_tool_call_needed.is_tool_call_needed:
|
177
181
|
# --- Code Execution Path ---
|
178
|
-
|
182
|
+
set_locals(__context__=context, __session__=session_messages)
|
183
|
+
code_execution: CodeExecutionResult = chatterer.exec(
|
179
184
|
messages=current_context,
|
180
185
|
repl_tool=repl_tool,
|
181
186
|
prompt_for_code_invoke=prompt_for_code_invoke,
|
@@ -200,15 +205,15 @@ def interactive_shell(
|
|
200
205
|
|
201
206
|
# --- Review Code Execution ---
|
202
207
|
current_context_after_exec = context + session_messages
|
203
|
-
decision = chatterer
|
204
|
-
|
205
|
-
messages=augment_prompt_for_toolcall(
|
208
|
+
decision = chatterer(
|
209
|
+
augment_prompt_for_toolcall(
|
206
210
|
function_signatures=function_signatures,
|
207
211
|
messages=current_context_after_exec,
|
208
212
|
prompt_for_code_invoke=prompt_for_code_invoke,
|
209
213
|
function_reference_prefix=function_reference_prefix,
|
210
214
|
function_reference_seperator=function_reference_seperator,
|
211
215
|
),
|
216
|
+
ReviewOnToolcall,
|
212
217
|
config=config,
|
213
218
|
stop=stop,
|
214
219
|
**kwargs,
|
@@ -233,15 +238,15 @@ def interactive_shell(
|
|
233
238
|
else:
|
234
239
|
# --- Thinking Path (No Code Needed) ---
|
235
240
|
current_context_before_think = context + session_messages
|
236
|
-
decision = chatterer
|
237
|
-
|
238
|
-
messages=augment_prompt_for_toolcall(
|
241
|
+
decision = chatterer(
|
242
|
+
augment_prompt_for_toolcall(
|
239
243
|
function_signatures=function_signatures,
|
240
244
|
messages=current_context_before_think,
|
241
245
|
prompt_for_code_invoke=prompt_for_code_invoke,
|
242
246
|
function_reference_prefix=function_reference_prefix,
|
243
247
|
function_reference_seperator=function_reference_seperator,
|
244
248
|
),
|
249
|
+
Think,
|
245
250
|
config=config,
|
246
251
|
stop=stop,
|
247
252
|
**kwargs,
|
@@ -277,10 +282,6 @@ def interactive_shell(
|
|
277
282
|
# Add the final AI response to the main context
|
278
283
|
context.append(AIMessage(content=response))
|
279
284
|
|
280
|
-
# --- Shell Initialization and Main Loop ---
|
281
|
-
if repl_tool is None:
|
282
|
-
repl_tool = get_default_repl_tool()
|
283
|
-
|
284
285
|
if additional_callables:
|
285
286
|
if callable(additional_callables):
|
286
287
|
additional_callables = [additional_callables]
|
@@ -321,15 +322,15 @@ def interactive_shell(
|
|
321
322
|
|
322
323
|
try:
|
323
324
|
# Initial planning step
|
324
|
-
initial_plan_decision = chatterer
|
325
|
-
|
326
|
-
messages=augment_prompt_for_toolcall(
|
325
|
+
initial_plan_decision = chatterer(
|
326
|
+
augment_prompt_for_toolcall(
|
327
327
|
function_signatures=function_signatures,
|
328
328
|
messages=context,
|
329
329
|
prompt_for_code_invoke=prompt_for_code_invoke,
|
330
330
|
function_reference_prefix=function_reference_prefix,
|
331
331
|
function_reference_seperator=function_reference_seperator,
|
332
332
|
),
|
333
|
+
ThinkBeforeSpeak,
|
333
334
|
config=config,
|
334
335
|
stop=stop,
|
335
336
|
**kwargs,
|
@@ -350,4 +351,4 @@ def interactive_shell(
|
|
350
351
|
|
351
352
|
|
352
353
|
if __name__ == "__main__":
|
353
|
-
interactive_shell()
|
354
|
+
interactive_shell(chatterer=Chatterer.openai())
|
chatterer/language_model.py
CHANGED
@@ -6,6 +6,7 @@ from typing import (
|
|
6
6
|
Callable,
|
7
7
|
Iterable,
|
8
8
|
Iterator,
|
9
|
+
Literal,
|
9
10
|
Optional,
|
10
11
|
Self,
|
11
12
|
Sequence,
|
@@ -20,7 +21,7 @@ from langchain_core.language_models.chat_models import BaseChatModel
|
|
20
21
|
from langchain_core.runnables.base import Runnable
|
21
22
|
from langchain_core.runnables.config import RunnableConfig
|
22
23
|
from langchain_core.utils.utils import secret_from_env
|
23
|
-
from pydantic import BaseModel, Field
|
24
|
+
from pydantic import BaseModel, Field, SecretStr
|
24
25
|
|
25
26
|
from .messages import AIMessage, BaseMessage, HumanMessage, UsageMetadata
|
26
27
|
from .utils.code_agent import CodeExecutionResult, FunctionSignature, augment_prompt_for_toolcall
|
@@ -65,57 +66,80 @@ class Chatterer(BaseModel):
|
|
65
66
|
|
66
67
|
@classmethod
|
67
68
|
def from_provider(
|
68
|
-
cls,
|
69
|
-
provider_and_model: str,
|
70
|
-
structured_output_kwargs: Optional[dict[str, Any]] = {"strict": True},
|
69
|
+
cls, provider_and_model: str, structured_output_kwargs: Optional[dict[str, object]] = {"strict": True}
|
71
70
|
) -> Self:
|
72
71
|
backend, model = provider_and_model.split(":", 1)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
return cls.anthropic(model_name=model, structured_output_kwargs=structured_output_kwargs)
|
77
|
-
elif backend == "google":
|
78
|
-
return cls.google(model=model, structured_output_kwargs=structured_output_kwargs)
|
79
|
-
elif backend == "ollama":
|
80
|
-
return cls.ollama(model=model, structured_output_kwargs=structured_output_kwargs)
|
81
|
-
elif backend == "openrouter":
|
82
|
-
return cls.open_router(model=model, structured_output_kwargs=structured_output_kwargs)
|
72
|
+
backends = cls.get_backends()
|
73
|
+
if func := backends.get(backend):
|
74
|
+
return func(model, structured_output_kwargs)
|
83
75
|
else:
|
84
|
-
raise ValueError(f"Unsupported backend
|
76
|
+
raise ValueError(f"Unsupported provider: {backend}. Supported providers are: {', '.join(backends.keys())}.")
|
77
|
+
|
78
|
+
@classmethod
|
79
|
+
def get_backends(cls) -> dict[str, Callable[[str, Optional[dict[str, object]]], Self]]:
|
80
|
+
return {
|
81
|
+
"openai": cls.openai,
|
82
|
+
"anthropic": cls.anthropic,
|
83
|
+
"google": cls.google,
|
84
|
+
"ollama": cls.ollama,
|
85
|
+
"openrouter": cls.open_router,
|
86
|
+
"xai": cls.xai,
|
87
|
+
}
|
85
88
|
|
86
89
|
@classmethod
|
87
90
|
def openai(
|
88
91
|
cls,
|
89
|
-
model: str = "gpt-
|
90
|
-
structured_output_kwargs: Optional[dict[str,
|
92
|
+
model: str = "gpt-4.1",
|
93
|
+
structured_output_kwargs: Optional[dict[str, object]] = {"strict": True},
|
94
|
+
api_key: Optional[str] = None,
|
95
|
+
**kwargs: Any,
|
91
96
|
) -> Self:
|
92
97
|
from langchain_openai import ChatOpenAI
|
93
98
|
|
94
|
-
return cls(
|
99
|
+
return cls(
|
100
|
+
client=ChatOpenAI(
|
101
|
+
model=model,
|
102
|
+
api_key=_get_api_key(api_key=api_key, env_key="OPENAI_API_KEY", raise_if_none=False),
|
103
|
+
**kwargs,
|
104
|
+
),
|
105
|
+
structured_output_kwargs=structured_output_kwargs or {},
|
106
|
+
)
|
95
107
|
|
96
108
|
@classmethod
|
97
109
|
def anthropic(
|
98
110
|
cls,
|
99
111
|
model_name: str = "claude-3-7-sonnet-20250219",
|
100
|
-
structured_output_kwargs: Optional[dict[str,
|
112
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
113
|
+
api_key: Optional[str] = None,
|
114
|
+
**kwargs: Any,
|
101
115
|
) -> Self:
|
102
116
|
from langchain_anthropic import ChatAnthropic
|
103
117
|
|
104
118
|
return cls(
|
105
|
-
client=ChatAnthropic(
|
119
|
+
client=ChatAnthropic(
|
120
|
+
model_name=model_name,
|
121
|
+
api_key=_get_api_key(api_key=api_key, env_key="ANTHROPIC_API_KEY", raise_if_none=True),
|
122
|
+
**kwargs,
|
123
|
+
),
|
106
124
|
structured_output_kwargs=structured_output_kwargs or {},
|
107
125
|
)
|
108
126
|
|
109
127
|
@classmethod
|
110
128
|
def google(
|
111
129
|
cls,
|
112
|
-
model: str = "gemini-2.
|
113
|
-
structured_output_kwargs: Optional[dict[str,
|
130
|
+
model: str = "gemini-2.5-flash-preview-04-17",
|
131
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
132
|
+
api_key: Optional[str] = None,
|
133
|
+
**kwargs: Any,
|
114
134
|
) -> Self:
|
115
135
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
116
136
|
|
117
137
|
return cls(
|
118
|
-
client=ChatGoogleGenerativeAI(
|
138
|
+
client=ChatGoogleGenerativeAI(
|
139
|
+
model=model,
|
140
|
+
api_key=_get_api_key(api_key=api_key, env_key="GOOGLE_API_KEY", raise_if_none=False),
|
141
|
+
**kwargs,
|
142
|
+
),
|
119
143
|
structured_output_kwargs=structured_output_kwargs or {},
|
120
144
|
)
|
121
145
|
|
@@ -123,12 +147,13 @@ class Chatterer(BaseModel):
|
|
123
147
|
def ollama(
|
124
148
|
cls,
|
125
149
|
model: str = "deepseek-r1:1.5b",
|
126
|
-
structured_output_kwargs: Optional[dict[str,
|
150
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
151
|
+
**kwargs: Any,
|
127
152
|
) -> Self:
|
128
153
|
from langchain_ollama import ChatOllama
|
129
154
|
|
130
155
|
return cls(
|
131
|
-
client=ChatOllama(model=model),
|
156
|
+
client=ChatOllama(model=model, **kwargs),
|
132
157
|
structured_output_kwargs=structured_output_kwargs or {},
|
133
158
|
)
|
134
159
|
|
@@ -136,7 +161,9 @@ class Chatterer(BaseModel):
|
|
136
161
|
def open_router(
|
137
162
|
cls,
|
138
163
|
model: str = "openrouter/quasar-alpha",
|
139
|
-
structured_output_kwargs: Optional[dict[str,
|
164
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
165
|
+
api_key: Optional[str] = None,
|
166
|
+
**kwargs: Any,
|
140
167
|
) -> Self:
|
141
168
|
from langchain_openai import ChatOpenAI
|
142
169
|
|
@@ -144,7 +171,29 @@ class Chatterer(BaseModel):
|
|
144
171
|
client=ChatOpenAI(
|
145
172
|
model=model,
|
146
173
|
base_url="https://openrouter.ai/api/v1",
|
147
|
-
api_key=
|
174
|
+
api_key=_get_api_key(api_key=api_key, env_key="OPENROUTER_API_KEY", raise_if_none=False),
|
175
|
+
**kwargs,
|
176
|
+
),
|
177
|
+
structured_output_kwargs=structured_output_kwargs or {},
|
178
|
+
)
|
179
|
+
|
180
|
+
@classmethod
|
181
|
+
def xai(
|
182
|
+
cls,
|
183
|
+
model: str = "grok-3-mini",
|
184
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
185
|
+
base_url: str = "https://api.x.ai/v1",
|
186
|
+
api_key: Optional[str] = None,
|
187
|
+
**kwargs: Any,
|
188
|
+
) -> Self:
|
189
|
+
from langchain_openai import ChatOpenAI
|
190
|
+
|
191
|
+
return cls(
|
192
|
+
client=ChatOpenAI(
|
193
|
+
model=model,
|
194
|
+
base_url=base_url,
|
195
|
+
api_key=_get_api_key(api_key=api_key, env_key="XAI_API_KEY", raise_if_none=False),
|
196
|
+
**kwargs,
|
148
197
|
),
|
149
198
|
structured_output_kwargs=structured_output_kwargs or {},
|
150
199
|
)
|
@@ -367,7 +416,7 @@ class Chatterer(BaseModel):
|
|
367
416
|
"total_tokens": approx_tokens,
|
368
417
|
}
|
369
418
|
|
370
|
-
def
|
419
|
+
def exec(
|
371
420
|
self,
|
372
421
|
messages: LanguageModelInput,
|
373
422
|
repl_tool: Optional["PythonAstREPLTool"] = None,
|
@@ -401,7 +450,12 @@ class Chatterer(BaseModel):
|
|
401
450
|
**kwargs,
|
402
451
|
)
|
403
452
|
|
404
|
-
|
453
|
+
@property
|
454
|
+
def invoke_code_execution(self) -> Callable[..., CodeExecutionResult]:
|
455
|
+
"""Alias for exec method for backward compatibility."""
|
456
|
+
return self.exec
|
457
|
+
|
458
|
+
async def aexec(
|
405
459
|
self,
|
406
460
|
messages: LanguageModelInput,
|
407
461
|
repl_tool: Optional["PythonAstREPLTool"] = None,
|
@@ -432,6 +486,11 @@ class Chatterer(BaseModel):
|
|
432
486
|
**kwargs,
|
433
487
|
)
|
434
488
|
|
489
|
+
@property
|
490
|
+
def ainvoke_code_execution(self):
|
491
|
+
"""Alias for aexec method for backward compatibility."""
|
492
|
+
return self.aexec
|
493
|
+
|
435
494
|
|
436
495
|
class PythonCodeToExecute(BaseModel):
|
437
496
|
code: str = Field(description="Python code to execute")
|
@@ -452,3 +511,23 @@ def _with_structured_output(
|
|
452
511
|
structured_output_kwargs: dict[str, Any],
|
453
512
|
) -> Runnable[LanguageModelInput, dict[object, object] | BaseModel]:
|
454
513
|
return client.with_structured_output(schema=response_model, **structured_output_kwargs) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
|
514
|
+
|
515
|
+
|
516
|
+
@overload
|
517
|
+
def _get_api_key(api_key: Optional[str], env_key: str, raise_if_none: Literal[True]) -> SecretStr: ...
|
518
|
+
@overload
|
519
|
+
def _get_api_key(api_key: Optional[str], env_key: str, raise_if_none: Literal[False]) -> Optional[SecretStr]: ...
|
520
|
+
def _get_api_key(api_key: Optional[str], env_key: str, raise_if_none: bool) -> Optional[SecretStr]:
|
521
|
+
if api_key is None:
|
522
|
+
api_key_found: SecretStr | None = secret_from_env(env_key, default=None)()
|
523
|
+
if raise_if_none and api_key_found is None:
|
524
|
+
raise ValueError(
|
525
|
+
(
|
526
|
+
f"Did not find API key, please add an environment variable"
|
527
|
+
f" `{env_key}` which contains it, or pass"
|
528
|
+
f" api_key as a named parameter."
|
529
|
+
)
|
530
|
+
)
|
531
|
+
return api_key_found
|
532
|
+
else:
|
533
|
+
return SecretStr(api_key)
|
chatterer/utils/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
from .base64_image import Base64Image
|
2
|
-
from .cli import ArgumentSpec, BaseArguments
|
3
2
|
from .code_agent import (
|
4
3
|
CodeExecutionResult,
|
5
4
|
FunctionSignature,
|
@@ -13,6 +12,4 @@ __all__ = [
|
|
13
12
|
"CodeExecutionResult",
|
14
13
|
"get_default_repl_tool",
|
15
14
|
"insert_callables_into_global",
|
16
|
-
"BaseArguments",
|
17
|
-
"ArgumentSpec",
|
18
15
|
]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: chatterer
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.17
|
4
4
|
Summary: The highest-level interface for various LLM APIs.
|
5
5
|
Requires-Python: >=3.12
|
6
6
|
Description-Content-Type: text/markdown
|
@@ -9,15 +9,18 @@ Requires-Dist: langchain>=0.3.19
|
|
9
9
|
Requires-Dist: langchain-openai>=0.3.11
|
10
10
|
Requires-Dist: pillow>=11.1.0
|
11
11
|
Requires-Dist: regex>=2024.11.6
|
12
|
+
Requires-Dist: rich>=13.9.4
|
12
13
|
Provides-Extra: dev
|
13
14
|
Requires-Dist: neo4j-extension>=0.1.14; extra == "dev"
|
14
15
|
Requires-Dist: colorama>=0.4.6; extra == "dev"
|
15
16
|
Requires-Dist: ipykernel>=6.29.5; extra == "dev"
|
17
|
+
Requires-Dist: spargear>=0.1.4; extra == "dev"
|
16
18
|
Provides-Extra: conversion
|
17
19
|
Requires-Dist: youtube-transcript-api>=1.0.3; extra == "conversion"
|
18
20
|
Requires-Dist: chatterer[browser]; extra == "conversion"
|
19
21
|
Requires-Dist: chatterer[pdf]; extra == "conversion"
|
20
22
|
Requires-Dist: chatterer[markdown]; extra == "conversion"
|
23
|
+
Requires-Dist: chatterer[video]; extra == "conversion"
|
21
24
|
Provides-Extra: browser
|
22
25
|
Requires-Dist: playwright>=1.50.0; extra == "browser"
|
23
26
|
Provides-Extra: pdf
|
@@ -28,6 +31,8 @@ Requires-Dist: markitdown[all]>=0.1.1; extra == "markdown"
|
|
28
31
|
Requires-Dist: markdownify>=1.1.0; extra == "markdown"
|
29
32
|
Requires-Dist: commonmark>=0.9.1; extra == "markdown"
|
30
33
|
Requires-Dist: mistune>=3.1.3; extra == "markdown"
|
34
|
+
Provides-Extra: video
|
35
|
+
Requires-Dist: pydub>=0.25.1; extra == "video"
|
31
36
|
Provides-Extra: langchain
|
32
37
|
Requires-Dist: chatterer[langchain-providers]; extra == "langchain"
|
33
38
|
Requires-Dist: langchain-experimental>=0.3.4; extra == "langchain"
|
@@ -1,6 +1,6 @@
|
|
1
|
-
chatterer/__init__.py,sha256=
|
2
|
-
chatterer/interactive.py,sha256=
|
3
|
-
chatterer/language_model.py,sha256=
|
1
|
+
chatterer/__init__.py,sha256=KmoWOad8UgqYeBCYXhFeeXCL9lzaB-Aa3KVqoAaXrMg,2174
|
2
|
+
chatterer/interactive.py,sha256=JTtIUbG_ABqmSKyQT_0vkq_K09ZCv4nyr_pAEMqhk88,16780
|
3
|
+
chatterer/language_model.py,sha256=j4MBUeHtifbufXyFUpKnxTlMbCwRQSgIgGmL09M4ifE,20126
|
4
4
|
chatterer/messages.py,sha256=SIvG9hMHaPG4AFadeRbj5yPnMq2J06fHA4D8jrkz4kQ,458
|
5
5
|
chatterer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
chatterer/common_types/__init__.py,sha256=Ais0kqzQJj4sED24xdv5azIxHkkj0vXWb4LC41dFkBQ,355
|
@@ -22,13 +22,12 @@ chatterer/tools/citation_chunking/citations.py,sha256=BWhSwzZccvu0Db-OxEbsuEGEz-
|
|
22
22
|
chatterer/tools/citation_chunking/prompt.py,sha256=so-8uFQ5b2Zq2V5Brfxd76bEnKYkHovYsohAnbxWEnY,7557
|
23
23
|
chatterer/tools/citation_chunking/reference.py,sha256=m47XYaB5uFff_x_k7US9hNr-SpZjKnl-GuzsGaQzcZo,893
|
24
24
|
chatterer/tools/citation_chunking/utils.py,sha256=Xytm9lMrS783Po1qWAdEJ8q7Q3l2UMzwHd9EkYTRiwk,6210
|
25
|
-
chatterer/utils/__init__.py,sha256=
|
25
|
+
chatterer/utils/__init__.py,sha256=of2NeLOjsAI79TgA4bL7UggCnAc7xT9eu3eeUBt9K8k,326
|
26
26
|
chatterer/utils/base64_image.py,sha256=w5SQoHVPDEQtFSUhV1l9GKg-IQy1WK5nyjv9k0upow8,10839
|
27
27
|
chatterer/utils/bytesio.py,sha256=QabdJCZsabPaiYVfJcdXzdiHjuhqDlz1vJuLJ60P7TY,2559
|
28
|
-
chatterer/utils/cli.py,sha256=F-Ooz4_pFeX4Y36R7OGhNrE7-IDyVIv2EEelYwJ4H0Y,19392
|
29
28
|
chatterer/utils/code_agent.py,sha256=3Wu0M6VvfvWKu4UUoJ0rV-WjvLj06r15_01vft3d1Ns,10231
|
30
29
|
chatterer/utils/imghdr.py,sha256=aZ1_AsRzyTsbV7uoeAZMVaC-hj73kvnFHMdHtFKskdE,3694
|
31
|
-
chatterer-0.1.
|
32
|
-
chatterer-0.1.
|
33
|
-
chatterer-0.1.
|
34
|
-
chatterer-0.1.
|
30
|
+
chatterer-0.1.17.dist-info/METADATA,sha256=tz1IPuxw7BSJR4N5SpT9FFE5Y9qE9twW9Hoxw18XF1I,11466
|
31
|
+
chatterer-0.1.17.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
32
|
+
chatterer-0.1.17.dist-info/top_level.txt,sha256=7nSQKP0bHxPRc7HyzdbKsJdkvPgYD0214o6slRizv9s,10
|
33
|
+
chatterer-0.1.17.dist-info/RECORD,,
|
chatterer/utils/cli.py
DELETED
@@ -1,476 +0,0 @@
|
|
1
|
-
import argparse
|
2
|
-
import io
|
3
|
-
import typing
|
4
|
-
import warnings
|
5
|
-
from dataclasses import dataclass, field, fields
|
6
|
-
from typing import (
|
7
|
-
IO,
|
8
|
-
Callable,
|
9
|
-
Dict,
|
10
|
-
Generic,
|
11
|
-
Iterable,
|
12
|
-
List,
|
13
|
-
Literal,
|
14
|
-
NamedTuple,
|
15
|
-
Optional,
|
16
|
-
Sequence,
|
17
|
-
Tuple,
|
18
|
-
TypeVar,
|
19
|
-
Union,
|
20
|
-
)
|
21
|
-
|
22
|
-
# --- Type Definitions ---
|
23
|
-
SUPPRESS_LITERAL_TYPE = Literal["==SUPPRESS=="]
|
24
|
-
SUPPRESS: SUPPRESS_LITERAL_TYPE = "==SUPPRESS=="
|
25
|
-
ACTION_TYPES_THAT_DONT_SUPPORT_TYPE_KWARG = (
|
26
|
-
"store_const",
|
27
|
-
"store_true",
|
28
|
-
"store_false",
|
29
|
-
"append_const",
|
30
|
-
"count",
|
31
|
-
"help",
|
32
|
-
"version",
|
33
|
-
)
|
34
|
-
Action = Optional[
|
35
|
-
Literal[
|
36
|
-
"store",
|
37
|
-
"store_const",
|
38
|
-
"store_true",
|
39
|
-
"store_false",
|
40
|
-
"append",
|
41
|
-
"append_const",
|
42
|
-
"count",
|
43
|
-
"help",
|
44
|
-
"version",
|
45
|
-
"extend",
|
46
|
-
]
|
47
|
-
]
|
48
|
-
T = TypeVar("T")
|
49
|
-
|
50
|
-
|
51
|
-
@dataclass
|
52
|
-
class ArgumentSpec(Generic[T]):
|
53
|
-
"""Represents the specification for a command-line argument."""
|
54
|
-
|
55
|
-
name_or_flags: List[str]
|
56
|
-
action: Action = None
|
57
|
-
nargs: Optional[Union[int, Literal["*", "+", "?"]]] = None
|
58
|
-
const: Optional[object] = None
|
59
|
-
default: Optional[Union[T, SUPPRESS_LITERAL_TYPE]] = None
|
60
|
-
choices: Optional[Sequence[T]] = None
|
61
|
-
required: bool = False
|
62
|
-
help: str = ""
|
63
|
-
metavar: Optional[str] = None
|
64
|
-
version: Optional[str] = None
|
65
|
-
type: Optional[Union[Callable[[str], T], argparse.FileType]] = None
|
66
|
-
value: Optional[T] = field(init=False, default=None) # Parsed value stored here
|
67
|
-
|
68
|
-
@property
|
69
|
-
def value_not_none(self) -> T:
|
70
|
-
"""Returns the value, raising an error if it's None."""
|
71
|
-
if self.value is None:
|
72
|
-
raise ValueError(f"Value for {self.name_or_flags} is None.")
|
73
|
-
return self.value
|
74
|
-
|
75
|
-
def get_add_argument_kwargs(self) -> Dict[str, object]:
|
76
|
-
"""Prepares keyword arguments for argparse.ArgumentParser.add_argument."""
|
77
|
-
kwargs: Dict[str, object] = {}
|
78
|
-
argparse_fields: set[str] = {f.name for f in fields(self) if f.name not in ("name_or_flags", "value")}
|
79
|
-
for field_name in argparse_fields:
|
80
|
-
attr_value = getattr(self, field_name)
|
81
|
-
if field_name == "default":
|
82
|
-
if attr_value is None:
|
83
|
-
pass # Keep default=None if explicitly set or inferred
|
84
|
-
elif attr_value in get_args(SUPPRESS_LITERAL_TYPE):
|
85
|
-
kwargs[field_name] = argparse.SUPPRESS
|
86
|
-
else:
|
87
|
-
kwargs[field_name] = attr_value
|
88
|
-
elif attr_value is not None:
|
89
|
-
if field_name == "type" and self.action in ACTION_TYPES_THAT_DONT_SUPPORT_TYPE_KWARG:
|
90
|
-
continue
|
91
|
-
kwargs[field_name] = attr_value
|
92
|
-
return kwargs
|
93
|
-
|
94
|
-
|
95
|
-
class ArgumentSpecType(NamedTuple):
|
96
|
-
T: object # The T in ArgumentSpec[T]
|
97
|
-
element_type: typing.Optional[typing.Type[object]] # The E in ArgumentSpec[List[E]] or ArgumentSpec[Tuple[E]]
|
98
|
-
|
99
|
-
@classmethod
|
100
|
-
def from_hint(cls, hints: Dict[str, object], attr_name: str):
|
101
|
-
if (
|
102
|
-
(hint := hints.get(attr_name))
|
103
|
-
and (hint_origin := get_origin(hint))
|
104
|
-
and (hint_args := get_args(hint))
|
105
|
-
and isinstance(hint_origin, type)
|
106
|
-
and issubclass(hint_origin, ArgumentSpec)
|
107
|
-
):
|
108
|
-
T: object = hint_args[0] # Extract T
|
109
|
-
element_type: typing.Optional[object] = None
|
110
|
-
if isinstance(outer_origin := get_origin(T), type):
|
111
|
-
if issubclass(outer_origin, list) and (args := get_args(T)):
|
112
|
-
element_type = args[0] # Extract E
|
113
|
-
elif issubclass(outer_origin, tuple) and (args := get_args(T)):
|
114
|
-
element_type = args # Extract E
|
115
|
-
else:
|
116
|
-
element_type = None # The E in ArgumentSpec[List[E]] or Tuple[E, ...]
|
117
|
-
if not isinstance(element_type, type):
|
118
|
-
element_type = None
|
119
|
-
return cls(T=T, element_type=element_type)
|
120
|
-
|
121
|
-
@property
|
122
|
-
def choices(self) -> typing.Optional[Tuple[object, ...]]:
|
123
|
-
# ArgumentSpec[Literal["A", "B"]] or ArgumentSpec[List[Literal["A", "B"]]]
|
124
|
-
T_origin = get_origin(self.T)
|
125
|
-
if (
|
126
|
-
isinstance(T_origin, type)
|
127
|
-
and (issubclass(T_origin, (list, tuple)))
|
128
|
-
and (args := get_args(self.T))
|
129
|
-
and (get_origin(arg := args[0]) is typing.Literal)
|
130
|
-
and (literals := get_args(arg))
|
131
|
-
):
|
132
|
-
return literals
|
133
|
-
elif T_origin is typing.Literal and (args := get_args(self.T)):
|
134
|
-
return args
|
135
|
-
|
136
|
-
@property
|
137
|
-
def type(self) -> typing.Optional[typing.Type[object]]:
|
138
|
-
if self.element_type is not None:
|
139
|
-
return self.element_type # If it's List[E] or Sequence[E], use E as type
|
140
|
-
elif self.T and isinstance(self.T, type):
|
141
|
-
return self.T # Use T as type
|
142
|
-
|
143
|
-
@property
|
144
|
-
def should_return_as_list(self) -> bool:
|
145
|
-
"""Determines if the argument should be returned as a list."""
|
146
|
-
T_origin = get_origin(self.T)
|
147
|
-
if isinstance(T_origin, type):
|
148
|
-
if issubclass(T_origin, list):
|
149
|
-
return True
|
150
|
-
return False
|
151
|
-
|
152
|
-
@property
|
153
|
-
def should_return_as_tuple(self) -> bool:
|
154
|
-
"""Determines if the argument should be returned as a tuple."""
|
155
|
-
T_origin = get_origin(self.T)
|
156
|
-
if isinstance(T_origin, type):
|
157
|
-
if issubclass(T_origin, tuple):
|
158
|
-
return True
|
159
|
-
return False
|
160
|
-
|
161
|
-
@property
|
162
|
-
def tuple_nargs(self) -> Optional[int]:
|
163
|
-
if self.should_return_as_tuple and (args := get_args(self.T)) and Ellipsis not in args:
|
164
|
-
return len(args)
|
165
|
-
|
166
|
-
|
167
|
-
class BaseArguments:
|
168
|
-
"""Base class for defining arguments declaratively using ArgumentSpec."""
|
169
|
-
|
170
|
-
__argspec__: Dict[str, ArgumentSpec[object]]
|
171
|
-
__argspectype__: Dict[str, ArgumentSpecType]
|
172
|
-
|
173
|
-
def __init_subclass__(cls, **kwargs: object) -> None:
|
174
|
-
"""
|
175
|
-
Processes ArgumentSpec definitions in subclasses upon class creation.
|
176
|
-
Automatically infers 'type' and 'choices' from type hints if possible.
|
177
|
-
"""
|
178
|
-
super().__init_subclass__(**kwargs)
|
179
|
-
cls.__argspec__ = {}
|
180
|
-
cls.__argspectype__ = {}
|
181
|
-
for current_cls in reversed(cls.__mro__):
|
182
|
-
if current_cls is object or current_cls is BaseArguments:
|
183
|
-
continue
|
184
|
-
current_vars = vars(current_cls)
|
185
|
-
try:
|
186
|
-
hints: Dict[str, object] = typing.get_type_hints(current_cls, globalns=dict(current_vars))
|
187
|
-
for attr_name, attr_value in current_vars.items():
|
188
|
-
if isinstance(attr_value, ArgumentSpec):
|
189
|
-
attr_value = typing.cast(ArgumentSpec[object], attr_value)
|
190
|
-
if arguments_spec_type := ArgumentSpecType.from_hint(hints=hints, attr_name=attr_name):
|
191
|
-
cls.__argspectype__[attr_name] = arguments_spec_type
|
192
|
-
if attr_value.choices is None and (literals := arguments_spec_type.choices):
|
193
|
-
attr_value.choices = literals
|
194
|
-
if attr_value.type is None and (type := arguments_spec_type.type):
|
195
|
-
attr_value.type = type
|
196
|
-
if tuple_nargs := arguments_spec_type.tuple_nargs:
|
197
|
-
attr_value.nargs = tuple_nargs
|
198
|
-
cls.__argspec__[attr_name] = attr_value
|
199
|
-
except Exception as e:
|
200
|
-
warnings.warn(f"Could not fully analyze type hints for {current_cls.__name__}: {e}", stacklevel=2)
|
201
|
-
for attr_name, attr_value in current_vars.items():
|
202
|
-
if isinstance(attr_value, ArgumentSpec) and attr_name not in cls.__argspec__:
|
203
|
-
cls.__argspec__[attr_name] = attr_value
|
204
|
-
|
205
|
-
@classmethod
|
206
|
-
def iter_specs(cls) -> Iterable[Tuple[str, ArgumentSpec[object]]]:
|
207
|
-
"""Iterates over the registered (attribute_name, ArgumentSpec) pairs."""
|
208
|
-
yield from cls.__argspec__.items()
|
209
|
-
|
210
|
-
@classmethod
|
211
|
-
def get_parser(cls) -> argparse.ArgumentParser:
|
212
|
-
"""Creates and configures an ArgumentParser based on the defined ArgumentSpecs."""
|
213
|
-
arg_parser = argparse.ArgumentParser(
|
214
|
-
description=cls.__doc__, # Use class docstring as description
|
215
|
-
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
216
|
-
add_help=False, # Add custom help argument later
|
217
|
-
)
|
218
|
-
# Add standard help argument
|
219
|
-
arg_parser.add_argument(
|
220
|
-
"-h", "--help", action="help", default=argparse.SUPPRESS, help="Show this help message and exit."
|
221
|
-
)
|
222
|
-
# Add arguments to the parser based on registered ArgumentSpecs
|
223
|
-
for key, spec in cls.iter_specs():
|
224
|
-
kwargs = spec.get_add_argument_kwargs()
|
225
|
-
# Determine if it's a positional or optional argument
|
226
|
-
is_positional: bool = not any(name.startswith("-") for name in spec.name_or_flags)
|
227
|
-
if is_positional:
|
228
|
-
# For positional args: remove 'required' (implicit), let argparse derive 'dest'
|
229
|
-
kwargs.pop("required", None)
|
230
|
-
try:
|
231
|
-
arg_parser.add_argument(*spec.name_or_flags, **kwargs) # pyright: ignore[reportArgumentType]
|
232
|
-
except Exception as e:
|
233
|
-
# Provide informative error message
|
234
|
-
raise ValueError(
|
235
|
-
f"Error adding positional argument '{key}' with spec {spec.name_or_flags} and kwargs {kwargs}: {e}"
|
236
|
-
) from e
|
237
|
-
else: # Optional argument
|
238
|
-
try:
|
239
|
-
# For optional args: explicitly set 'dest' to the attribute name ('key')
|
240
|
-
arg_parser.add_argument(*spec.name_or_flags, dest=key, **kwargs) # pyright: ignore[reportArgumentType]
|
241
|
-
except Exception as e:
|
242
|
-
# Provide informative error message
|
243
|
-
raise ValueError(
|
244
|
-
f"Error adding optional argument '{key}' with spec {spec.name_or_flags} and kwargs {kwargs}: {e}"
|
245
|
-
) from e
|
246
|
-
return arg_parser
|
247
|
-
|
248
|
-
@classmethod
|
249
|
-
def load(cls, args: Optional[Sequence[str]] = None) -> None:
|
250
|
-
"""
|
251
|
-
Parses command-line arguments and assigns the values to the corresponding ArgumentSpec instances.
|
252
|
-
If 'args' is None, uses sys.argv[1:].
|
253
|
-
"""
|
254
|
-
parser = cls.get_parser()
|
255
|
-
try:
|
256
|
-
parsed_args = parser.parse_args(args)
|
257
|
-
except SystemExit as e:
|
258
|
-
# Allow SystemExit (e.g., from --help) to propagate
|
259
|
-
raise e
|
260
|
-
# Assign parsed values from the namespace
|
261
|
-
cls.load_from_namespace(parsed_args)
|
262
|
-
|
263
|
-
@classmethod
|
264
|
-
def load_from_namespace(cls, args: argparse.Namespace) -> None:
|
265
|
-
"""Assigns values from a parsed argparse.Namespace object to the ArgumentSpecs."""
|
266
|
-
for key, spec in cls.iter_specs():
|
267
|
-
# Determine the attribute name in the namespace
|
268
|
-
# Positional args use their name, optionals use the 'dest' (which is 'key')
|
269
|
-
is_positional = not any(name.startswith("-") for name in spec.name_or_flags)
|
270
|
-
attr_name = spec.name_or_flags[0] if is_positional else key
|
271
|
-
# Check if the attribute exists in the namespace
|
272
|
-
if not hasattr(args, attr_name):
|
273
|
-
continue
|
274
|
-
|
275
|
-
value: object = getattr(args, attr_name)
|
276
|
-
if value is argparse.SUPPRESS:
|
277
|
-
continue
|
278
|
-
|
279
|
-
# Assign the value unless it's the SUPPRESS sentinel
|
280
|
-
if argument_spec_type := cls.__argspectype__.get(key):
|
281
|
-
if argument_spec_type.should_return_as_list:
|
282
|
-
if isinstance(value, list):
|
283
|
-
value = typing.cast(List[object], value)
|
284
|
-
elif value is not None:
|
285
|
-
value = [value]
|
286
|
-
elif argument_spec_type.should_return_as_tuple:
|
287
|
-
if isinstance(value, tuple):
|
288
|
-
value = typing.cast(Tuple[object, ...], value)
|
289
|
-
elif value is not None:
|
290
|
-
if isinstance(value, list):
|
291
|
-
value = tuple(typing.cast(List[object], value))
|
292
|
-
else:
|
293
|
-
value = (value,)
|
294
|
-
spec.value = value
|
295
|
-
|
296
|
-
@classmethod
|
297
|
-
def get_value(cls, key: str) -> Optional[object]:
|
298
|
-
"""Retrieves the parsed value for a specific argument by its attribute name."""
|
299
|
-
if key in cls.__argspec__:
|
300
|
-
return cls.__argspec__[key].value
|
301
|
-
raise KeyError(f"Argument spec with key '{key}' not found.")
|
302
|
-
|
303
|
-
@classmethod
|
304
|
-
def get_all_values(cls) -> Dict[str, Optional[object]]:
|
305
|
-
"""Returns a dictionary of all argument attribute names and their parsed values."""
|
306
|
-
return {key: spec.value for key, spec in cls.iter_specs()}
|
307
|
-
|
308
|
-
def __init__(self) -> None:
|
309
|
-
self.load()
|
310
|
-
|
311
|
-
|
312
|
-
def get_args(t: object) -> Tuple[object, ...]:
|
313
|
-
"""Returns the arguments of a type or a generic type."""
|
314
|
-
return typing.get_args(t)
|
315
|
-
|
316
|
-
|
317
|
-
def get_origin(t: object) -> typing.Optional[object]:
|
318
|
-
"""Returns the origin of a type or a generic type."""
|
319
|
-
return typing.get_origin(t)
|
320
|
-
|
321
|
-
|
322
|
-
# --- Main execution block (Example Usage) ---
|
323
|
-
if __name__ == "__main__":
|
324
|
-
|
325
|
-
class __Arguments(BaseArguments):
|
326
|
-
"""Example argument parser demonstrating various features."""
|
327
|
-
|
328
|
-
my_str_arg: ArgumentSpec[str] = ArgumentSpec(
|
329
|
-
["-s", "--string-arg"], default="Hello", help="A string argument.", metavar="TEXT"
|
330
|
-
)
|
331
|
-
my_int_arg: ArgumentSpec[int] = ArgumentSpec(
|
332
|
-
["-i", "--integer-arg"], required=True, help="A required integer argument."
|
333
|
-
)
|
334
|
-
verbose: ArgumentSpec[bool] = ArgumentSpec(
|
335
|
-
["-v", "--verbose"], action="store_true", help="Increase output verbosity."
|
336
|
-
)
|
337
|
-
# --- List<str> ---
|
338
|
-
my_list_arg: ArgumentSpec[List[str]] = ArgumentSpec(
|
339
|
-
["--list-values"],
|
340
|
-
nargs="+",
|
341
|
-
help="One or more string values.",
|
342
|
-
default=None,
|
343
|
-
)
|
344
|
-
# --- Positional IO ---
|
345
|
-
input_file: ArgumentSpec[IO[str]] = ArgumentSpec(
|
346
|
-
["input_file"],
|
347
|
-
type=argparse.FileType("r", encoding="utf-8"),
|
348
|
-
help="Path to the input file (required).",
|
349
|
-
metavar="INPUT_PATH",
|
350
|
-
)
|
351
|
-
output_file: ArgumentSpec[Optional[IO[str]]] = ArgumentSpec(
|
352
|
-
["output_file"],
|
353
|
-
type=argparse.FileType("w", encoding="utf-8"),
|
354
|
-
nargs="?",
|
355
|
-
default=None,
|
356
|
-
help="Optional output file path.",
|
357
|
-
metavar="OUTPUT_PATH",
|
358
|
-
)
|
359
|
-
# --- Simple Literal (choices auto-detected) ---
|
360
|
-
log_level: ArgumentSpec[Literal["DEBUG", "INFO", "WARNING", "ERROR"]] = ArgumentSpec(
|
361
|
-
["--log-level"],
|
362
|
-
default="INFO",
|
363
|
-
help="Set the logging level.",
|
364
|
-
)
|
365
|
-
# --- Literal + explicit choices (explicit wins) ---
|
366
|
-
mode: ArgumentSpec[Literal["fast", "slow", "careful"]] = ArgumentSpec(
|
367
|
-
["--mode"],
|
368
|
-
choices=["fast", "slow"], # Explicit choices override Literal args
|
369
|
-
default="fast",
|
370
|
-
help="Operation mode.",
|
371
|
-
)
|
372
|
-
# --- List[Literal] (choices auto-detected) ---
|
373
|
-
enabled_features: ArgumentSpec[List[Literal["CACHE", "LOGGING", "RETRY"]]] = ArgumentSpec(
|
374
|
-
["--features"],
|
375
|
-
nargs="*", # 0 or more features
|
376
|
-
help="Enable specific features.",
|
377
|
-
default=[],
|
378
|
-
)
|
379
|
-
tuple_features: ArgumentSpec[
|
380
|
-
Tuple[Literal["CACHE", "LOGGING", "RETRY"], Literal["CAwCHE", "LOGGING", "RETRY"]]
|
381
|
-
] = ArgumentSpec(
|
382
|
-
["--tuple-features"],
|
383
|
-
help="Enable specific features (tuple).",
|
384
|
-
)
|
385
|
-
|
386
|
-
# --- SUPPRESS default ---
|
387
|
-
optional_flag: ArgumentSpec[str] = ArgumentSpec(
|
388
|
-
["--opt-flag"],
|
389
|
-
default=SUPPRESS,
|
390
|
-
help="An optional flag whose attribute might not be set.",
|
391
|
-
)
|
392
|
-
|
393
|
-
print("--- Initial State (Before Parsing) ---")
|
394
|
-
parser_for_debug = __Arguments.get_parser()
|
395
|
-
for k, s in __Arguments.iter_specs():
|
396
|
-
print(f"{k}: value={s.value}, type={s.type}, choices={s.choices}") # Check inferred choices
|
397
|
-
|
398
|
-
dummy_input_filename = "temp_input_for_argparse_test.txt"
|
399
|
-
try:
|
400
|
-
with open(dummy_input_filename, "w", encoding="utf-8") as f:
|
401
|
-
f.write("This is a test file.\n")
|
402
|
-
print(f"\nCreated dummy input file: {dummy_input_filename}")
|
403
|
-
except Exception as e:
|
404
|
-
print(f"Warning: Could not create dummy input file '{dummy_input_filename}': {e}")
|
405
|
-
|
406
|
-
# Example command-line arguments (Adjusted order)
|
407
|
-
test_args = [
|
408
|
-
dummy_input_filename,
|
409
|
-
"-i",
|
410
|
-
"42",
|
411
|
-
"--log-level",
|
412
|
-
"WARNING",
|
413
|
-
"--mode",
|
414
|
-
"slow",
|
415
|
-
"--list-values",
|
416
|
-
"apple",
|
417
|
-
"banana",
|
418
|
-
"--features",
|
419
|
-
"CACHE",
|
420
|
-
"RETRY", # Test List[Literal]
|
421
|
-
"--tuple-features",
|
422
|
-
"CACHE",
|
423
|
-
"LOGGING", # Test Tuple[Literal]
|
424
|
-
]
|
425
|
-
# test_args = ['--features', 'INVALID'] # Test invalid choice for List[Literal]
|
426
|
-
# test_args = ['-h']
|
427
|
-
|
428
|
-
try:
|
429
|
-
print(f"\n--- Loading Arguments (Args: {test_args if test_args else 'from sys.argv'}) ---")
|
430
|
-
__Arguments.load(test_args)
|
431
|
-
print("\n--- Final Loaded Arguments ---")
|
432
|
-
all_values = __Arguments.get_all_values()
|
433
|
-
for key, value in all_values.items():
|
434
|
-
value_type = type(value).__name__
|
435
|
-
if isinstance(value, io.IOBase):
|
436
|
-
try:
|
437
|
-
name = getattr(value, "name", "<unknown_name>")
|
438
|
-
mode = getattr(value, "mode", "?")
|
439
|
-
value_repr = f"<IO {name} mode='{mode}'>"
|
440
|
-
except ValueError:
|
441
|
-
value_repr = "<IO object (closed)>"
|
442
|
-
else:
|
443
|
-
value_repr = repr(value)
|
444
|
-
print(f"{key}: {value_repr} (Type: {value_type})")
|
445
|
-
|
446
|
-
print("\n--- Accessing Specific Values ---")
|
447
|
-
print(f"Features : {__Arguments.get_value('enabled_features')}") # Check List[Literal] value
|
448
|
-
|
449
|
-
input_f = __Arguments.get_value("input_file")
|
450
|
-
if isinstance(input_f, io.IOBase):
|
451
|
-
try:
|
452
|
-
print(f"\nReading from input file: {input_f.name}") # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
453
|
-
input_f.close()
|
454
|
-
print(f"Closed input file: {input_f.name}") # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
455
|
-
except Exception as e:
|
456
|
-
print(f"Error processing input file: {e}")
|
457
|
-
|
458
|
-
except SystemExit as e:
|
459
|
-
print(f"\nExiting application (SystemExit code: {e.code}).")
|
460
|
-
except FileNotFoundError as e:
|
461
|
-
print(f"\nError: Required file not found: {e.filename}")
|
462
|
-
parser_for_debug.print_usage()
|
463
|
-
except Exception as e:
|
464
|
-
print(f"\nAn unexpected error occurred: {e}")
|
465
|
-
import traceback
|
466
|
-
|
467
|
-
traceback.print_exc()
|
468
|
-
finally:
|
469
|
-
import os
|
470
|
-
|
471
|
-
if os.path.exists(dummy_input_filename):
|
472
|
-
try:
|
473
|
-
os.remove(dummy_input_filename)
|
474
|
-
print(f"\nRemoved dummy input file: {dummy_input_filename}")
|
475
|
-
except Exception as e:
|
476
|
-
print(f"Warning: Could not remove dummy file '{dummy_input_filename}': {e}")
|
File without changes
|