kash-shell 0.3.9__py3-none-any.whl → 0.3.11__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.
- kash/actions/__init__.py +4 -4
- kash/actions/core/format_markdown_template.py +2 -5
- kash/actions/core/markdownify.py +7 -6
- kash/actions/core/readability.py +7 -6
- kash/actions/core/render_as_html.py +37 -0
- kash/actions/core/show_webpage.py +6 -11
- kash/actions/core/strip_html.py +2 -6
- kash/actions/core/tabbed_webpage_config.py +31 -0
- kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -4
- kash/commands/__init__.py +8 -20
- kash/commands/base/basic_file_commands.py +15 -0
- kash/commands/base/debug_commands.py +13 -0
- kash/commands/base/files_command.py +28 -10
- kash/commands/base/general_commands.py +21 -16
- kash/commands/base/logs_commands.py +4 -2
- kash/commands/base/model_commands.py +8 -8
- kash/commands/base/search_command.py +3 -2
- kash/commands/base/show_command.py +5 -3
- kash/commands/extras/parse_uv_lock.py +186 -0
- kash/commands/help/doc_commands.py +2 -31
- kash/commands/help/welcome.py +33 -0
- kash/commands/workspace/selection_commands.py +11 -6
- kash/commands/workspace/workspace_commands.py +19 -17
- kash/config/colors.py +3 -1
- kash/config/env_settings.py +14 -1
- kash/config/init.py +2 -2
- kash/config/logger.py +59 -56
- kash/config/logger_basic.py +3 -3
- kash/config/settings.py +116 -57
- kash/config/setup.py +28 -12
- kash/config/text_styles.py +3 -13
- kash/docs/load_api_docs.py +2 -1
- kash/docs/markdown/topics/a3_getting_started.md +3 -2
- kash/{concepts → embeddings}/text_similarity.py +2 -2
- kash/exec/__init__.py +20 -3
- kash/exec/action_decorators.py +24 -10
- kash/exec/action_exec.py +41 -23
- kash/exec/action_registry.py +13 -48
- kash/exec/command_registry.py +2 -1
- kash/exec/fetch_url_metadata.py +4 -6
- kash/exec/importing.py +56 -0
- kash/exec/llm_transforms.py +12 -10
- kash/exec/precondition_registry.py +2 -1
- kash/exec/preconditions.py +22 -1
- kash/exec/resolve_args.py +4 -0
- kash/exec/shell_callable_action.py +33 -19
- kash/file_storage/file_store.py +42 -27
- kash/file_storage/item_file_format.py +5 -2
- kash/file_storage/metadata_dirs.py +11 -2
- kash/help/assistant.py +1 -1
- kash/help/assistant_instructions.py +2 -1
- kash/help/function_param_info.py +1 -1
- kash/help/help_embeddings.py +2 -2
- kash/help/help_printing.py +7 -11
- kash/llm_utils/clean_headings.py +1 -1
- kash/llm_utils/llm_api_keys.py +4 -4
- kash/llm_utils/llm_features.py +68 -0
- kash/llm_utils/llm_messages.py +1 -2
- kash/llm_utils/llm_names.py +1 -1
- kash/llm_utils/llms.py +8 -3
- kash/local_server/__init__.py +5 -2
- kash/local_server/local_server.py +8 -5
- kash/local_server/local_server_commands.py +2 -2
- kash/local_server/local_server_routes.py +1 -7
- kash/local_server/local_url_formatters.py +1 -1
- kash/mcp/__init__.py +5 -2
- kash/mcp/mcp_cli.py +5 -5
- kash/mcp/mcp_server_commands.py +5 -5
- kash/mcp/mcp_server_routes.py +5 -5
- kash/mcp/mcp_server_sse.py +4 -2
- kash/media_base/media_cache.py +8 -8
- kash/media_base/media_services.py +1 -1
- kash/media_base/media_tools.py +6 -6
- kash/media_base/services/local_file_media.py +2 -2
- kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -110
- kash/media_base/transcription_format.py +73 -0
- kash/media_base/transcription_whisper.py +38 -0
- kash/model/__init__.py +73 -5
- kash/model/actions_model.py +38 -4
- kash/model/concept_model.py +30 -0
- kash/model/items_model.py +115 -32
- kash/model/params_model.py +24 -0
- kash/shell/completions/completion_scoring.py +37 -5
- kash/shell/output/kerm_codes.py +1 -2
- kash/shell/output/shell_formatting.py +14 -4
- kash/shell/shell_main.py +2 -2
- kash/shell/utils/exception_printing.py +6 -0
- kash/shell/utils/native_utils.py +26 -20
- kash/shell/utils/shell_function_wrapper.py +15 -15
- kash/text_handling/custom_sliding_transforms.py +12 -4
- kash/text_handling/doc_normalization.py +6 -2
- kash/text_handling/markdown_render.py +118 -0
- kash/text_handling/markdown_utils.py +226 -0
- kash/utils/common/function_inspect.py +360 -110
- kash/utils/common/import_utils.py +12 -3
- kash/utils/common/type_utils.py +0 -29
- kash/utils/common/url.py +27 -3
- kash/utils/errors.py +6 -0
- kash/utils/file_utils/file_ext.py +4 -0
- kash/utils/file_utils/file_formats.py +2 -2
- kash/utils/file_utils/file_formats_model.py +20 -1
- kash/web_content/dir_store.py +1 -2
- kash/web_content/file_cache_utils.py +37 -10
- kash/web_content/file_processing.py +68 -0
- kash/web_content/local_file_cache.py +12 -9
- kash/web_content/web_extract.py +8 -3
- kash/web_content/web_fetch.py +12 -4
- kash/web_gen/__init__.py +0 -4
- kash/web_gen/simple_webpage.py +52 -0
- kash/web_gen/tabbed_webpage.py +24 -14
- kash/web_gen/template_render.py +37 -2
- kash/web_gen/templates/base_styles.css.jinja +169 -43
- kash/web_gen/templates/base_webpage.html.jinja +110 -45
- kash/web_gen/templates/content_styles.css.jinja +4 -2
- kash/web_gen/templates/item_view.html.jinja +49 -39
- kash/web_gen/templates/simple_webpage.html.jinja +24 -0
- kash/web_gen/templates/tabbed_webpage.html.jinja +42 -33
- kash/workspaces/__init__.py +15 -2
- kash/workspaces/selections.py +18 -3
- kash/workspaces/source_items.py +0 -1
- kash/workspaces/workspaces.py +5 -11
- kash/xonsh_custom/command_nl_utils.py +40 -19
- kash/xonsh_custom/custom_shell.py +43 -11
- kash/xonsh_custom/customize_prompt.py +39 -21
- kash/xonsh_custom/load_into_xonsh.py +22 -25
- kash/xonsh_custom/shell_load_commands.py +2 -2
- kash/xonsh_custom/xonsh_completers.py +2 -249
- kash/xonsh_custom/xonsh_keybindings.py +282 -0
- kash/xonsh_custom/xonsh_modern_tools.py +3 -3
- kash/xontrib/kash_extension.py +5 -6
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/METADATA +10 -8
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/RECORD +137 -136
- kash/actions/core/webpage_config.py +0 -21
- kash/concepts/concept_formats.py +0 -23
- kash/shell/clideps/api_keys.py +0 -100
- kash/shell/clideps/dotenv_setup.py +0 -115
- kash/shell/clideps/dotenv_utils.py +0 -98
- kash/shell/clideps/pkg_deps.py +0 -257
- kash/shell/clideps/platforms.py +0 -11
- kash/shell/clideps/terminal_features.py +0 -56
- kash/shell/utils/osc_utils.py +0 -95
- kash/shell/utils/terminal_images.py +0 -133
- kash/text_handling/markdown_util.py +0 -167
- kash/utils/common/atomic_var.py +0 -171
- kash/utils/common/string_replace.py +0 -93
- kash/utils/common/string_template.py +0 -101
- /kash/{concepts → embeddings}/cosine.py +0 -0
- /kash/{concepts → embeddings}/embeddings.py +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,111 +2,172 @@ import inspect
|
|
|
2
2
|
import types
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
5
6
|
from inspect import Parameter
|
|
6
|
-
from typing import Any, Union, cast, get_args, get_origin # pyright: ignore
|
|
7
|
+
from typing import Any, Union, cast, get_args, get_origin # pyright: ignore[reportDeprecated]
|
|
7
8
|
|
|
8
|
-
NO_DEFAULT = Parameter.empty
|
|
9
|
+
NO_DEFAULT = Parameter.empty # Alias for clarity
|
|
10
|
+
|
|
11
|
+
ParameterKind = Parameter.POSITIONAL_ONLY.__class__
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
@dataclass(frozen=True)
|
|
12
15
|
class FuncParam:
|
|
16
|
+
"""
|
|
17
|
+
Hold structured information about a single function parameter.
|
|
18
|
+
"""
|
|
19
|
+
|
|
13
20
|
name: str
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
kind: ParameterKind # e.g., POSITIONAL_OR_KEYWORD
|
|
22
|
+
annotation: Any # The raw type annotation (can be Parameter.empty)
|
|
23
|
+
default: Any # The raw default value (can be Parameter.empty)
|
|
24
|
+
position: int | None # Position index for positional args, None for keyword-only
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
# Resolved type information
|
|
27
|
+
# This is the concrete, simplified main type (e.g., str from str | None, list from list[int])
|
|
28
|
+
effective_type: type | None
|
|
29
|
+
inner_type: type | None # For collections, the type of elements (e.g., str from list[str])
|
|
30
|
+
is_explicitly_optional: bool # True if original annotation was Optional[X] or X | None
|
|
23
31
|
|
|
24
32
|
@property
|
|
25
33
|
def has_default(self) -> bool:
|
|
26
34
|
"""Does this parameter have a default value?"""
|
|
27
35
|
return self.default != NO_DEFAULT
|
|
28
36
|
|
|
37
|
+
@property
|
|
38
|
+
def is_pure_positional(self) -> bool:
|
|
39
|
+
"""Is this a plain or varargs positional parameter, with no default value?"""
|
|
40
|
+
return self.position is not None and not self.has_default
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def is_positional_only(self) -> bool:
|
|
44
|
+
"""Is a true positional parameter, with no default value."""
|
|
45
|
+
return self.kind == Parameter.POSITIONAL_ONLY
|
|
29
46
|
|
|
30
|
-
|
|
47
|
+
@property
|
|
48
|
+
def is_positional_or_keyword(self) -> bool:
|
|
49
|
+
return self.kind == Parameter.POSITIONAL_OR_KEYWORD
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def is_varargs(self) -> bool:
|
|
53
|
+
return self.kind == Parameter.VAR_POSITIONAL or self.kind == Parameter.VAR_KEYWORD
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def is_keyword_only(self) -> bool:
|
|
57
|
+
return self.kind == Parameter.KEYWORD_ONLY
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _resolve_type_details(annotation: Any) -> tuple[type | None, type | None, bool]:
|
|
31
61
|
"""
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Note that technically, parameters are of 5 types, positional-only,
|
|
40
|
-
positional-or-keyword, keyword-only, varargs, or varargs-keyword, which is what
|
|
41
|
-
`inspect` tells you, but in practice you typically just want to know
|
|
42
|
-
`FuncParam.is_positional()`, i.e. if it is a pure positional parameter (no default)
|
|
43
|
-
and not a keyword (with a default).
|
|
62
|
+
Resolves an annotation into (effective_type, inner_type, is_explicitly_optional).
|
|
63
|
+
|
|
64
|
+
- effective_type: The main type (e.g., str from Optional[str], list from list[int]).
|
|
65
|
+
- inner_type: The type of elements if effective_type is a collection
|
|
66
|
+
(e.g., int from list[int]). For non-collection or unparameterized
|
|
67
|
+
collection, this is None.
|
|
68
|
+
- is_explicitly_optional: True if the annotation was X | None or Optional[X].
|
|
44
69
|
"""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
params: list[FuncParam] = []
|
|
70
|
+
if annotation is Parameter.empty or annotation is Any:
|
|
71
|
+
return (None, None, False)
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Parameter.POSITIONAL_ONLY,
|
|
52
|
-
Parameter.POSITIONAL_OR_KEYWORD,
|
|
53
|
-
Parameter.VAR_POSITIONAL,
|
|
54
|
-
)
|
|
55
|
-
is_varargs = param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD)
|
|
56
|
-
has_default = param.default != NO_DEFAULT
|
|
57
|
-
|
|
58
|
-
# Get type from type annotation or default value.
|
|
59
|
-
param_type: type | None = None
|
|
60
|
-
if param.annotation != Parameter.empty:
|
|
61
|
-
param_type = _extract_simple_type(param.annotation)
|
|
62
|
-
elif param.default is not Parameter.empty:
|
|
63
|
-
param_type = type(param.default)
|
|
64
|
-
|
|
65
|
-
func_param = FuncParam(
|
|
66
|
-
name=param.name,
|
|
67
|
-
type=param_type,
|
|
68
|
-
default=param.default if has_default else NO_DEFAULT,
|
|
69
|
-
position=i + 1 if is_positional else None,
|
|
70
|
-
is_varargs=is_varargs,
|
|
71
|
-
)
|
|
72
|
-
params.append(func_param)
|
|
73
|
+
current_annotation = annotation
|
|
74
|
+
is_optional_flag = False
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
# Unwrap Optional[T] or T | None (from Python 3.10+ UnionType)
|
|
77
|
+
origin: type | None = get_origin(current_annotation)
|
|
78
|
+
args = get_args(current_annotation)
|
|
79
|
+
|
|
80
|
+
if origin is Union or ( # pyright: ignore[reportDeprecated]
|
|
81
|
+
hasattr(types, "UnionType") and isinstance(current_annotation, types.UnionType)
|
|
82
|
+
):
|
|
83
|
+
non_none_args = [arg for arg in args if arg is not type(None)]
|
|
84
|
+
if len(non_none_args) == 1:
|
|
85
|
+
is_optional_flag = True
|
|
86
|
+
current_annotation = non_none_args[0]
|
|
87
|
+
origin = get_origin(current_annotation) # Re-evaluate for the unwrapped type
|
|
88
|
+
args = get_args(current_annotation)
|
|
89
|
+
elif not non_none_args: # Handles Union[NoneType] or just NoneType
|
|
90
|
+
return (type(None), None, True)
|
|
91
|
+
# If multiple non_none_args (e.g., int | str), current_annotation remains the Union for now.
|
|
92
|
+
|
|
93
|
+
# Determine effective_type and inner_type from (potentially unwrapped) current_annotation
|
|
94
|
+
final_effective_type: type | None = None
|
|
95
|
+
final_inner_type: type | None = None
|
|
96
|
+
|
|
97
|
+
if isinstance(
|
|
98
|
+
current_annotation, type
|
|
99
|
+
): # Covers simple types (int, str) and resolved Union types (int | str)
|
|
100
|
+
final_effective_type = current_annotation
|
|
101
|
+
elif origin and isinstance(origin, type): # Generics like list, dict, tuple (e.g., list[str])
|
|
102
|
+
final_effective_type = cast(type, origin) # This would be `list` for `list[str]`
|
|
103
|
+
if args and _is_type_tuple(args) and args[0] is not Any:
|
|
104
|
+
# For simplicity, take the first type argument as inner_type.
|
|
105
|
+
# E.g., for list[str], inner_type is str. For dict[str, int], inner_type is str.
|
|
106
|
+
final_inner_type = args[0]
|
|
107
|
+
# A more sophisticated approach might handle all args for tuples like tuple[str, int]
|
|
108
|
+
|
|
109
|
+
return final_effective_type, final_inner_type, is_optional_flag
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _is_type_tuple(args: tuple[Any, ...]) -> bool:
|
|
113
|
+
"""Are all args types?"""
|
|
114
|
+
if not args:
|
|
115
|
+
return False
|
|
116
|
+
return all(isinstance(arg, type) for arg in args)
|
|
75
117
|
|
|
76
118
|
|
|
77
|
-
def
|
|
119
|
+
def inspect_function_params(func: Callable[..., Any], unwrap: bool = True) -> list[FuncParam]:
|
|
78
120
|
"""
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
121
|
+
Inspects a Python function's signature and returns a list of `ParamInfo` objects.
|
|
122
|
+
A convenience wrapper for `inspect.signature` that provides a detailed, structured
|
|
123
|
+
representation of each parameter, making it easier to build tools like CLI argument
|
|
124
|
+
parsers. By default, it unwraps decorated functions to get to the original signature.
|
|
82
125
|
"""
|
|
83
|
-
|
|
84
|
-
|
|
126
|
+
unwrapped_func = inspect.unwrap(func) if unwrap else func
|
|
127
|
+
signature = inspect.signature(unwrapped_func)
|
|
85
128
|
|
|
86
|
-
|
|
87
|
-
if hasattr(types, "UnionType") and isinstance(annotation, types.UnionType):
|
|
88
|
-
args = get_args(annotation)
|
|
89
|
-
non_none_args = [arg for arg in args if arg is not type(None)]
|
|
90
|
-
if len(non_none_args) == 1 and isinstance(non_none_args[0], type):
|
|
91
|
-
return non_none_args[0]
|
|
129
|
+
param_infos: list[FuncParam] = []
|
|
92
130
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
131
|
+
for i, (param_name, param_obj) in enumerate(signature.parameters.items()):
|
|
132
|
+
effective_type, inner_type, is_optional = _resolve_type_details(param_obj.annotation)
|
|
133
|
+
|
|
134
|
+
# Fallback: if type is not resolved from annotation, try to infer from default value.
|
|
135
|
+
if (
|
|
136
|
+
effective_type is None
|
|
137
|
+
and param_obj.default is not NO_DEFAULT
|
|
138
|
+
and param_obj.default is not None
|
|
139
|
+
):
|
|
140
|
+
if not is_optional: # Avoid setting NoneType if it was Optional[SomethingElse]
|
|
141
|
+
effective_type = type(param_obj.default)
|
|
142
|
+
|
|
143
|
+
# Determine position
|
|
144
|
+
is_positional = param_obj.kind in (
|
|
145
|
+
Parameter.POSITIONAL_ONLY,
|
|
146
|
+
Parameter.POSITIONAL_OR_KEYWORD,
|
|
147
|
+
Parameter.VAR_POSITIONAL,
|
|
148
|
+
)
|
|
149
|
+
position = i + 1 if is_positional else None
|
|
150
|
+
|
|
151
|
+
info = FuncParam(
|
|
152
|
+
name=param_name,
|
|
153
|
+
kind=param_obj.kind,
|
|
154
|
+
annotation=param_obj.annotation, # Store raw annotation
|
|
155
|
+
default=param_obj.default, # Store raw default
|
|
156
|
+
position=position,
|
|
157
|
+
effective_type=effective_type,
|
|
158
|
+
inner_type=inner_type,
|
|
159
|
+
is_explicitly_optional=is_optional,
|
|
160
|
+
)
|
|
161
|
+
param_infos.append(info)
|
|
102
162
|
|
|
103
|
-
return
|
|
163
|
+
return param_infos
|
|
104
164
|
|
|
105
165
|
|
|
106
166
|
## Tests
|
|
107
167
|
|
|
108
168
|
|
|
109
|
-
def
|
|
169
|
+
def test_inspect_function_parameters_updated():
|
|
170
|
+
# Test functions from your original example
|
|
110
171
|
def func0(path: str | None = None) -> list:
|
|
111
172
|
return [path]
|
|
112
173
|
|
|
@@ -124,55 +185,244 @@ def test_inspect_function_params():
|
|
|
124
185
|
def func4() -> list:
|
|
125
186
|
return []
|
|
126
187
|
|
|
127
|
-
def func5(x: int, y: int = 3, *, z: int = 4, **kwargs):
|
|
128
|
-
|
|
188
|
+
def func5(x: int, y: int = 3, *, z: int = 4, **kwargs):
|
|
189
|
+
return [x, y, z, kwargs]
|
|
129
190
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
191
|
+
class MyEnum(Enum):
|
|
192
|
+
ITEM1 = "item1"
|
|
193
|
+
ITEM2 = "item2"
|
|
194
|
+
|
|
195
|
+
def func6(opt_enum: MyEnum | None = MyEnum.ITEM1):
|
|
196
|
+
return [opt_enum]
|
|
136
197
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
print()
|
|
140
|
-
print(repr(params1))
|
|
141
|
-
print()
|
|
142
|
-
print(repr(params2))
|
|
143
|
-
print()
|
|
144
|
-
print(repr(params3))
|
|
145
|
-
print()
|
|
146
|
-
print(repr(params4))
|
|
147
|
-
print()
|
|
148
|
-
print(repr(params5))
|
|
149
|
-
|
|
150
|
-
assert params0 == [FuncParam(name="path", type=str, default=None, position=1, is_varargs=False)]
|
|
198
|
+
def func7(numbers: list[int]):
|
|
199
|
+
return [numbers]
|
|
151
200
|
|
|
201
|
+
def func8(maybe_list: list[str] | None = None):
|
|
202
|
+
return [maybe_list]
|
|
203
|
+
|
|
204
|
+
params0 = inspect_function_params(func0)
|
|
205
|
+
print("\ninspect_function_parameters results:")
|
|
206
|
+
print(f"func0: {params0}")
|
|
207
|
+
assert params0 == [
|
|
208
|
+
FuncParam(
|
|
209
|
+
name="path",
|
|
210
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
211
|
+
annotation=(str | None),
|
|
212
|
+
default=None,
|
|
213
|
+
position=1,
|
|
214
|
+
effective_type=str,
|
|
215
|
+
inner_type=None,
|
|
216
|
+
is_explicitly_optional=True,
|
|
217
|
+
)
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
params1 = inspect_function_params(func1)
|
|
221
|
+
print(f"func1: {params1}")
|
|
152
222
|
assert params1 == [
|
|
153
|
-
FuncParam(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
223
|
+
FuncParam(
|
|
224
|
+
name="arg1",
|
|
225
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
226
|
+
annotation=str,
|
|
227
|
+
default=NO_DEFAULT,
|
|
228
|
+
position=1,
|
|
229
|
+
effective_type=str,
|
|
230
|
+
inner_type=None,
|
|
231
|
+
is_explicitly_optional=False,
|
|
232
|
+
),
|
|
233
|
+
FuncParam(
|
|
234
|
+
name="arg2",
|
|
235
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
236
|
+
annotation=str,
|
|
237
|
+
default=NO_DEFAULT,
|
|
238
|
+
position=2,
|
|
239
|
+
effective_type=str,
|
|
240
|
+
inner_type=None,
|
|
241
|
+
is_explicitly_optional=False,
|
|
242
|
+
),
|
|
243
|
+
FuncParam(
|
|
244
|
+
name="arg3",
|
|
245
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
246
|
+
annotation=int,
|
|
247
|
+
default=NO_DEFAULT,
|
|
248
|
+
position=3,
|
|
249
|
+
effective_type=int,
|
|
250
|
+
inner_type=None,
|
|
251
|
+
is_explicitly_optional=False,
|
|
252
|
+
),
|
|
253
|
+
FuncParam(
|
|
254
|
+
name="option_one",
|
|
255
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
256
|
+
annotation=bool,
|
|
257
|
+
default=False,
|
|
258
|
+
position=4,
|
|
259
|
+
effective_type=bool,
|
|
260
|
+
inner_type=None,
|
|
261
|
+
is_explicitly_optional=False,
|
|
262
|
+
), # bool default makes it not explicitly Optional from type
|
|
263
|
+
FuncParam(
|
|
264
|
+
name="option_two",
|
|
265
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
266
|
+
annotation=(str | None),
|
|
267
|
+
default=None,
|
|
268
|
+
position=5,
|
|
269
|
+
effective_type=str,
|
|
270
|
+
inner_type=None,
|
|
271
|
+
is_explicitly_optional=True,
|
|
272
|
+
),
|
|
158
273
|
]
|
|
159
274
|
|
|
275
|
+
params2 = inspect_function_params(func2)
|
|
276
|
+
print(f"func2: {params2}")
|
|
160
277
|
assert params2 == [
|
|
161
|
-
FuncParam(
|
|
162
|
-
|
|
163
|
-
|
|
278
|
+
FuncParam(
|
|
279
|
+
name="paths",
|
|
280
|
+
kind=Parameter.VAR_POSITIONAL,
|
|
281
|
+
annotation=str,
|
|
282
|
+
default=NO_DEFAULT,
|
|
283
|
+
position=1,
|
|
284
|
+
effective_type=str,
|
|
285
|
+
inner_type=None,
|
|
286
|
+
is_explicitly_optional=False,
|
|
287
|
+
), # For *args: T, effective_type is T
|
|
288
|
+
FuncParam(
|
|
289
|
+
name="summary",
|
|
290
|
+
kind=Parameter.KEYWORD_ONLY,
|
|
291
|
+
annotation=(bool | None),
|
|
292
|
+
default=False,
|
|
293
|
+
position=None,
|
|
294
|
+
effective_type=bool,
|
|
295
|
+
inner_type=None,
|
|
296
|
+
is_explicitly_optional=True,
|
|
297
|
+
),
|
|
298
|
+
FuncParam(
|
|
299
|
+
name="iso_time",
|
|
300
|
+
kind=Parameter.KEYWORD_ONLY,
|
|
301
|
+
annotation=bool,
|
|
302
|
+
default=False,
|
|
303
|
+
position=None,
|
|
304
|
+
effective_type=bool,
|
|
305
|
+
inner_type=None,
|
|
306
|
+
is_explicitly_optional=False,
|
|
307
|
+
),
|
|
164
308
|
]
|
|
165
309
|
|
|
310
|
+
params3 = inspect_function_params(func3)
|
|
311
|
+
print(f"func3: {params3}")
|
|
166
312
|
assert params3 == [
|
|
167
|
-
FuncParam(
|
|
168
|
-
|
|
313
|
+
FuncParam(
|
|
314
|
+
name="arg1",
|
|
315
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
316
|
+
annotation=str,
|
|
317
|
+
default=NO_DEFAULT,
|
|
318
|
+
position=1,
|
|
319
|
+
effective_type=str,
|
|
320
|
+
inner_type=None,
|
|
321
|
+
is_explicitly_optional=False,
|
|
322
|
+
),
|
|
323
|
+
FuncParam(
|
|
324
|
+
name="keywords",
|
|
325
|
+
kind=Parameter.VAR_KEYWORD,
|
|
326
|
+
annotation=Parameter.empty,
|
|
327
|
+
default=NO_DEFAULT,
|
|
328
|
+
position=None,
|
|
329
|
+
effective_type=None,
|
|
330
|
+
inner_type=None,
|
|
331
|
+
is_explicitly_optional=False,
|
|
332
|
+
),
|
|
169
333
|
]
|
|
170
334
|
|
|
335
|
+
params4 = inspect_function_params(func4)
|
|
336
|
+
print(f"func4: {params4}")
|
|
171
337
|
assert params4 == []
|
|
172
338
|
|
|
339
|
+
params5 = inspect_function_params(func5)
|
|
340
|
+
print(f"func5: {params5}")
|
|
173
341
|
assert params5 == [
|
|
174
|
-
FuncParam(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
342
|
+
FuncParam(
|
|
343
|
+
name="x",
|
|
344
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
345
|
+
annotation=int,
|
|
346
|
+
default=NO_DEFAULT,
|
|
347
|
+
position=1,
|
|
348
|
+
effective_type=int,
|
|
349
|
+
inner_type=None,
|
|
350
|
+
is_explicitly_optional=False,
|
|
351
|
+
),
|
|
352
|
+
FuncParam(
|
|
353
|
+
name="y",
|
|
354
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
355
|
+
annotation=int,
|
|
356
|
+
default=3,
|
|
357
|
+
position=2,
|
|
358
|
+
effective_type=int,
|
|
359
|
+
inner_type=None,
|
|
360
|
+
is_explicitly_optional=False,
|
|
361
|
+
),
|
|
362
|
+
FuncParam(
|
|
363
|
+
name="z",
|
|
364
|
+
kind=Parameter.KEYWORD_ONLY,
|
|
365
|
+
annotation=int,
|
|
366
|
+
default=4,
|
|
367
|
+
position=None,
|
|
368
|
+
effective_type=int,
|
|
369
|
+
inner_type=None,
|
|
370
|
+
is_explicitly_optional=False,
|
|
371
|
+
),
|
|
372
|
+
FuncParam(
|
|
373
|
+
name="kwargs",
|
|
374
|
+
kind=Parameter.VAR_KEYWORD,
|
|
375
|
+
annotation=Parameter.empty,
|
|
376
|
+
default=NO_DEFAULT,
|
|
377
|
+
position=None,
|
|
378
|
+
effective_type=None,
|
|
379
|
+
inner_type=None,
|
|
380
|
+
is_explicitly_optional=False,
|
|
381
|
+
),
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
params6 = inspect_function_params(func6)
|
|
385
|
+
print(f"func6: {params6}")
|
|
386
|
+
|
|
387
|
+
assert params6 == [
|
|
388
|
+
FuncParam(
|
|
389
|
+
name="opt_enum",
|
|
390
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
391
|
+
annotation=(MyEnum | None),
|
|
392
|
+
default=MyEnum.ITEM1,
|
|
393
|
+
position=1,
|
|
394
|
+
effective_type=MyEnum,
|
|
395
|
+
inner_type=None,
|
|
396
|
+
is_explicitly_optional=True,
|
|
397
|
+
)
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
params7 = inspect_function_params(func7)
|
|
401
|
+
print(f"func7: {params7}")
|
|
402
|
+
assert params7 == [
|
|
403
|
+
FuncParam(
|
|
404
|
+
name="numbers",
|
|
405
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
406
|
+
annotation=list[int],
|
|
407
|
+
default=NO_DEFAULT,
|
|
408
|
+
position=1,
|
|
409
|
+
effective_type=list,
|
|
410
|
+
inner_type=int,
|
|
411
|
+
is_explicitly_optional=False,
|
|
412
|
+
)
|
|
413
|
+
]
|
|
414
|
+
|
|
415
|
+
params8 = inspect_function_params(func8)
|
|
416
|
+
print(f"func8: {params8}")
|
|
417
|
+
assert params8 == [
|
|
418
|
+
FuncParam(
|
|
419
|
+
name="maybe_list",
|
|
420
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
421
|
+
annotation=(list[str] | None),
|
|
422
|
+
default=None,
|
|
423
|
+
position=1,
|
|
424
|
+
effective_type=list,
|
|
425
|
+
inner_type=str,
|
|
426
|
+
is_explicitly_optional=True,
|
|
427
|
+
)
|
|
178
428
|
]
|
|
@@ -15,21 +15,30 @@ Tallies: TypeAlias = dict[str, int]
|
|
|
15
15
|
def import_subdirs(
|
|
16
16
|
parent_package_name: str,
|
|
17
17
|
parent_dir: Path,
|
|
18
|
-
subdir_names: list[str],
|
|
18
|
+
subdir_names: list[str] | None = None,
|
|
19
19
|
tallies: Tallies | None = None,
|
|
20
20
|
):
|
|
21
21
|
"""
|
|
22
22
|
Import all files in the given subdirectories of a single parent directory.
|
|
23
|
+
Wraps `pkgutil.iter_modules` to iterate over all modules in the subdirectories.
|
|
24
|
+
If `subdir_names` is `None`, will import all subdirectories.
|
|
23
25
|
"""
|
|
24
26
|
if tallies is None:
|
|
25
27
|
tallies = {}
|
|
28
|
+
if not subdir_names:
|
|
29
|
+
subdir_names = ["."]
|
|
26
30
|
|
|
27
31
|
for subdir_name in subdir_names:
|
|
28
|
-
|
|
32
|
+
if subdir_name == ".":
|
|
33
|
+
full_path = parent_dir
|
|
34
|
+
package_name = parent_package_name
|
|
35
|
+
else:
|
|
36
|
+
full_path = parent_dir / subdir_name
|
|
37
|
+
package_name = f"{parent_package_name}.{subdir_name}"
|
|
38
|
+
|
|
29
39
|
if not full_path.is_dir():
|
|
30
40
|
raise FileNotFoundError(f"Subdirectory not found: {full_path}")
|
|
31
41
|
|
|
32
|
-
package_name = f"{parent_package_name}.{subdir_name}"
|
|
33
42
|
for _module_finder, module_name, _is_pkg in pkgutil.iter_modules(path=[str(full_path)]):
|
|
34
43
|
importlib.import_module(f"{package_name}.{module_name}") # Propagate import errors
|
|
35
44
|
tallies[package_name] = tallies.get(package_name, 0) + 1
|
kash/utils/common/type_utils.py
CHANGED
|
@@ -15,35 +15,6 @@ def not_none(value: T | None, message: str | None = None) -> T:
|
|
|
15
15
|
return value
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def is_truthy(value: Any, strict: bool = True) -> bool:
|
|
19
|
-
"""
|
|
20
|
-
True for all common string and non-string values for true. Useful for parsing
|
|
21
|
-
string values or command line arguments.
|
|
22
|
-
"""
|
|
23
|
-
truthy_values = {"true", "1", "yes", "on", "y"}
|
|
24
|
-
falsy_values = {"false", "0", "no", "off", "n", ""}
|
|
25
|
-
|
|
26
|
-
if value is None:
|
|
27
|
-
return False
|
|
28
|
-
elif isinstance(value, str):
|
|
29
|
-
value = value.strip().lower()
|
|
30
|
-
if value in truthy_values:
|
|
31
|
-
return True
|
|
32
|
-
elif value in falsy_values:
|
|
33
|
-
return False
|
|
34
|
-
elif isinstance(value, (int, float)):
|
|
35
|
-
return value != 0
|
|
36
|
-
elif isinstance(value, bool):
|
|
37
|
-
return value
|
|
38
|
-
elif isinstance(value, (list, tuple, set, dict)):
|
|
39
|
-
return len(value) > 0
|
|
40
|
-
|
|
41
|
-
if strict:
|
|
42
|
-
raise ValueError(f"Could not convert type {type(value)} to boolean: {repr(value)}")
|
|
43
|
-
|
|
44
|
-
return bool(value)
|
|
45
|
-
|
|
46
|
-
|
|
47
18
|
def as_dataclass(dict_data: dict[str, Any], dataclass_type: type[T]) -> T:
|
|
48
19
|
"""
|
|
49
20
|
Convert a dict recursively to dataclass object, raising an error if the data does
|
kash/utils/common/url.py
CHANGED
|
@@ -67,15 +67,39 @@ def is_file_url(url: str | Url) -> bool:
|
|
|
67
67
|
return url.startswith("file://")
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def
|
|
70
|
+
def parse_http_url(url: str | Url) -> ParseResult:
|
|
71
71
|
"""
|
|
72
|
-
Parse
|
|
72
|
+
Parse an http/https URL and return the parsed result, raising ValueError if
|
|
73
|
+
not an http/https URL.
|
|
74
|
+
"""
|
|
75
|
+
parsed_url = urlparse(url)
|
|
76
|
+
if parsed_url.scheme in ("http", "https"):
|
|
77
|
+
return parsed_url
|
|
78
|
+
else:
|
|
79
|
+
raise ValueError(f"Not an http/https URL: {url}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def parse_file_url(url: str | Url) -> Path:
|
|
83
|
+
"""
|
|
84
|
+
Parse a file URL and return the path, raising ValueError if not a file URL.
|
|
73
85
|
"""
|
|
74
86
|
parsed_url = urlparse(url)
|
|
75
87
|
if parsed_url.scheme == "file":
|
|
76
88
|
return Path(parsed_url.path)
|
|
77
89
|
else:
|
|
78
|
-
|
|
90
|
+
raise ValueError(f"Not a file URL: {url}")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def parse_s3_url(url: str | Url) -> tuple[str, str]:
|
|
94
|
+
"""
|
|
95
|
+
Parse an S3 URL and return the bucket and key, raising ValueError if not an
|
|
96
|
+
S3 URL.
|
|
97
|
+
"""
|
|
98
|
+
parsed_url = urlparse(url)
|
|
99
|
+
if parsed_url.scheme == "s3":
|
|
100
|
+
return parsed_url.netloc, parsed_url.path.lstrip("/")
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError(f"Not an S3 URL: {url}")
|
|
79
103
|
|
|
80
104
|
|
|
81
105
|
def as_file_url(path: str | Path) -> Url:
|
kash/utils/errors.py
CHANGED
|
@@ -139,6 +139,12 @@ class FileFormatError(ContentError):
|
|
|
139
139
|
pass
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
class ApiError(KashRuntimeError):
|
|
143
|
+
"""Raised when an API call returns something unexpected."""
|
|
144
|
+
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
|
|
142
148
|
def _nonfatal_exceptions() -> tuple[type[Exception], ...]:
|
|
143
149
|
exceptions: list[type[Exception]] = [SelfExplanatoryError, FileNotFoundError, IOError]
|
|
144
150
|
try:
|
|
@@ -20,6 +20,7 @@ class FileExt(Enum):
|
|
|
20
20
|
diff = "diff"
|
|
21
21
|
json = "json"
|
|
22
22
|
csv = "csv"
|
|
23
|
+
xlsx = "xlsx"
|
|
23
24
|
npz = "npz"
|
|
24
25
|
log = "log"
|
|
25
26
|
py = "py"
|
|
@@ -34,6 +35,8 @@ class FileExt(Enum):
|
|
|
34
35
|
mp3 = "mp3"
|
|
35
36
|
m4a = "m4a"
|
|
36
37
|
mp4 = "mp4"
|
|
38
|
+
pptx = "pptx"
|
|
39
|
+
epub = "epub"
|
|
37
40
|
|
|
38
41
|
@property
|
|
39
42
|
def dot_ext(self) -> str:
|
|
@@ -50,6 +53,7 @@ class FileExt(Enum):
|
|
|
50
53
|
self.py,
|
|
51
54
|
self.sh,
|
|
52
55
|
self.xsh,
|
|
56
|
+
self.epub,
|
|
53
57
|
]
|
|
54
58
|
|
|
55
59
|
@property
|