python-corekit 0.4.1__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- corekit/connections/sql/connection.py +6 -2
- corekit/html/__init__.py +22 -0
- corekit/html/client.py +22 -0
- corekit/html/models.py +20 -0
- corekit/html/parser.py +272 -0
- corekit/html/scraper.py +127 -0
- corekit/html/settings.py +197 -0
- corekit/html/state.py +56 -0
- corekit/http/__init__.py +2 -1
- corekit/http/api.py +71 -3
- corekit/http/client.py +24 -54
- corekit/http/response.py +38 -5
- corekit/llm/client.py +3 -2
- corekit/llm/tools/loop.py +5 -2
- corekit/llm/wire.py +11 -5
- corekit/schemas/__init__.py +8 -1
- corekit/schemas/models/arbitrary.py +49 -5
- corekit/utils/__init__.py +48 -7
- corekit/utils/coercion.py +45 -59
- corekit/utils/collections.py +136 -3
- corekit/utils/parsers.py +205 -0
- {python_corekit-0.4.1.dist-info → python_corekit-0.5.0.dist-info}/METADATA +8 -7
- {python_corekit-0.4.1.dist-info → python_corekit-0.5.0.dist-info}/RECORD +26 -18
- {python_corekit-0.4.1.dist-info → python_corekit-0.5.0.dist-info}/WHEEL +0 -0
- {python_corekit-0.4.1.dist-info → python_corekit-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {python_corekit-0.4.1.dist-info → python_corekit-0.5.0.dist-info}/top_level.txt +0 -0
corekit/utils/coercion.py
CHANGED
|
@@ -6,17 +6,36 @@ empty value rather than raising. Use them at boundaries where the input is
|
|
|
6
6
|
genuinely unknown -- an API payload, a config value, a scraped field -- and a
|
|
7
7
|
crash would be worse than a default.
|
|
8
8
|
|
|
9
|
+
These are thin wrappers around ``corekit.utils.parsers``. Extraction logic lives
|
|
10
|
+
there once; here we only fix the fallback to the type's empty value so the
|
|
11
|
+
return type is guaranteed.
|
|
12
|
+
|
|
9
13
|
The tradeoff is real: ``safe_int("banana")`` is ``0``, indistinguishable from a
|
|
10
|
-
genuine zero. Where telling those apart matters,
|
|
14
|
+
genuine zero. Where telling those apart matters, call the parser directly and
|
|
15
|
+
inspect ``ParsedValue.success``, or pass a sentinel ``default``.
|
|
11
16
|
"""
|
|
12
17
|
|
|
13
|
-
import json
|
|
14
|
-
from datetime import date, datetime
|
|
15
18
|
from typing import Any
|
|
16
19
|
|
|
17
|
-
from corekit.utils.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
from corekit.utils.parsers import (
|
|
21
|
+
parse_dict,
|
|
22
|
+
parse_float,
|
|
23
|
+
parse_int,
|
|
24
|
+
parse_list,
|
|
25
|
+
parse_set,
|
|
26
|
+
parse_string,
|
|
27
|
+
parse_tuple,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"safe_dict",
|
|
32
|
+
"safe_float",
|
|
33
|
+
"safe_int",
|
|
34
|
+
"safe_list",
|
|
35
|
+
"safe_set",
|
|
36
|
+
"safe_string",
|
|
37
|
+
"safe_tuple",
|
|
38
|
+
]
|
|
20
39
|
|
|
21
40
|
|
|
22
41
|
def safe_string(item: Any, pretty: bool = False) -> str:
|
|
@@ -27,54 +46,21 @@ def safe_string(item: Any, pretty: bool = False) -> str:
|
|
|
27
46
|
list instead -- ``"a, b, c"``, or ``"key: value, ..."`` for a mapping --
|
|
28
47
|
which reads better in a notification than raw JSON.
|
|
29
48
|
"""
|
|
30
|
-
|
|
31
|
-
item = item.decode("utf-8", errors="ignore")
|
|
32
|
-
if isinstance(item, str):
|
|
33
|
-
return item.strip()
|
|
34
|
-
# bool is a subclass of int, so both land here and render as "True"/"1".
|
|
35
|
-
if isinstance(item, (int, float)):
|
|
36
|
-
return str(item)
|
|
37
|
-
if isinstance(item, (list, tuple, set, dict)):
|
|
38
|
-
if pretty:
|
|
39
|
-
if isinstance(item, dict):
|
|
40
|
-
return list_to_english([f"{key}: {value}" for key, value in item.items()])
|
|
41
|
-
return list_to_english([safe_string(element) for element in item])
|
|
42
|
-
return json.dumps(item)
|
|
43
|
-
if isinstance(item, (date, datetime)):
|
|
44
|
-
return item.isoformat()
|
|
45
|
-
return ""
|
|
49
|
+
return parse_string(item, pretty=pretty, default="").value
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
def safe_int(item: Any) -> int:
|
|
49
53
|
"""
|
|
50
54
|
Convert anything to an int, or ``0`` if it will not convert.
|
|
51
55
|
"""
|
|
52
|
-
|
|
53
|
-
return item
|
|
54
|
-
if isinstance(item, bytes):
|
|
55
|
-
item = item.decode("utf-8", errors="ignore")
|
|
56
|
-
if isinstance(item, (float, str)):
|
|
57
|
-
try:
|
|
58
|
-
return int(item)
|
|
59
|
-
except ValueError:
|
|
60
|
-
return 0
|
|
61
|
-
return 0
|
|
56
|
+
return parse_int(item, default=0).value
|
|
62
57
|
|
|
63
58
|
|
|
64
59
|
def safe_float(item: Any) -> float:
|
|
65
60
|
"""
|
|
66
61
|
Convert anything to a float, or ``0.0`` if it will not convert.
|
|
67
62
|
"""
|
|
68
|
-
|
|
69
|
-
return item
|
|
70
|
-
if isinstance(item, bytes):
|
|
71
|
-
item = item.decode("utf-8", errors="ignore")
|
|
72
|
-
if isinstance(item, (int, str)):
|
|
73
|
-
try:
|
|
74
|
-
return float(item)
|
|
75
|
-
except ValueError:
|
|
76
|
-
return 0.0
|
|
77
|
-
return 0.0
|
|
63
|
+
return parse_float(item, default=0.0).value
|
|
78
64
|
|
|
79
65
|
|
|
80
66
|
def safe_list(item: Any) -> list[Any]:
|
|
@@ -84,13 +70,7 @@ def safe_list(item: Any) -> list[Any]:
|
|
|
84
70
|
A mapping yields its values. A string is *not* treated as a collection --
|
|
85
71
|
exploding it into characters is almost never what the caller meant.
|
|
86
72
|
"""
|
|
87
|
-
|
|
88
|
-
return item
|
|
89
|
-
if isinstance(item, (tuple, set)):
|
|
90
|
-
return list(item)
|
|
91
|
-
if isinstance(item, dict):
|
|
92
|
-
return list(item.values())
|
|
93
|
-
return []
|
|
73
|
+
return parse_list(item, default=[]).value
|
|
94
74
|
|
|
95
75
|
|
|
96
76
|
def safe_tuple(item: Any) -> tuple[Any, ...]:
|
|
@@ -100,19 +80,25 @@ def safe_tuple(item: Any) -> tuple[Any, ...]:
|
|
|
100
80
|
A mapping yields its values. A string is *not* treated as a collection --
|
|
101
81
|
exploding it into characters is almost never what the caller meant.
|
|
102
82
|
"""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
83
|
+
return parse_tuple(item, default=()).value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def safe_set(item: Any) -> set[Any]:
|
|
87
|
+
"""
|
|
88
|
+
Convert a collection to a set, or ``set()`` for anything else.
|
|
89
|
+
|
|
90
|
+
A mapping yields its values. A string is *not* treated as a collection --
|
|
91
|
+
exploding it into characters is almost never what the caller meant.
|
|
92
|
+
Unhashable elements yield ``set()``.
|
|
93
|
+
"""
|
|
94
|
+
return parse_set(item, default=set()).value
|
|
110
95
|
|
|
111
96
|
|
|
112
97
|
def safe_dict(item: Any) -> dict[str, Any]:
|
|
113
98
|
"""
|
|
114
99
|
Return the mapping unchanged, or ``{}`` for anything else.
|
|
100
|
+
|
|
101
|
+
Also accepts objects that expose ``to_dict``, ``model_dump``, or ``dict``,
|
|
102
|
+
and falls back to ``__dict__`` when present.
|
|
115
103
|
"""
|
|
116
|
-
|
|
117
|
-
return item
|
|
118
|
-
return {}
|
|
104
|
+
return parse_dict(item, default={}).value
|
corekit/utils/collections.py
CHANGED
|
@@ -7,10 +7,29 @@ once. Built for JSON from somewhere you do not control, where any level may be
|
|
|
7
7
|
missing or the wrong type.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
from typing import Any, Callable
|
|
10
|
+
from __future__ import annotations
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
import inspect
|
|
13
|
+
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Mapping
|
|
14
|
+
from copy import deepcopy
|
|
15
|
+
from typing import Any, Awaitable, Callable, Iterator, Literal
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"MultiMatch",
|
|
19
|
+
"aiterator",
|
|
20
|
+
"attr_or_key",
|
|
21
|
+
"iterator",
|
|
22
|
+
"keygetter",
|
|
23
|
+
"kv_string",
|
|
24
|
+
"repeated_get",
|
|
25
|
+
"split_list",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
WhereSeq = Callable[[Any], bool]
|
|
29
|
+
WhereMap = Callable[[Any, Any], bool]
|
|
30
|
+
AsyncWhereSeq = Callable[[Any], bool | Awaitable[bool]]
|
|
31
|
+
AsyncWhereMap = Callable[[Any, Any], bool | Awaitable[bool]]
|
|
32
|
+
IteratorMode = Literal["keys", "values", "items"]
|
|
14
33
|
|
|
15
34
|
KEY_SEPARATOR = "."
|
|
16
35
|
|
|
@@ -40,6 +59,120 @@ def attr_or_key(obj: Any, name: str) -> Any:
|
|
|
40
59
|
return getattr(obj, name, None)
|
|
41
60
|
|
|
42
61
|
|
|
62
|
+
def _mapping_item(key: Any, value: Any, mode: IteratorMode) -> Any:
|
|
63
|
+
if mode == "values":
|
|
64
|
+
return value
|
|
65
|
+
if mode == "items":
|
|
66
|
+
return key, value
|
|
67
|
+
return key
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def iterator(
|
|
71
|
+
obj: Iterable[Any] | Mapping[Any, Any] | None = None,
|
|
72
|
+
*,
|
|
73
|
+
mode: IteratorMode = "keys",
|
|
74
|
+
where: WhereSeq | WhereMap | None = None,
|
|
75
|
+
compact: bool = False,
|
|
76
|
+
) -> Iterator[Any]:
|
|
77
|
+
"""
|
|
78
|
+
Iterate an iterable or mapping, optionally filtered.
|
|
79
|
+
|
|
80
|
+
Non-mappings ignore ``mode`` and yield elements. Mappings yield keys,
|
|
81
|
+
values, or items according to ``mode`` (default ``keys``).
|
|
82
|
+
|
|
83
|
+
``compact=True`` skips ``None`` items, or mapping entries whose **value**
|
|
84
|
+
is ``None``. ``where`` is a sync predicate: one argument for iterables,
|
|
85
|
+
``(key, value)`` for mappings — the entry is filtered, then ``mode``
|
|
86
|
+
selects what is yielded.
|
|
87
|
+
|
|
88
|
+
``None`` yields nothing.
|
|
89
|
+
"""
|
|
90
|
+
if obj is None:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if isinstance(obj, Mapping):
|
|
94
|
+
for key, value in obj.items():
|
|
95
|
+
if compact and value is None:
|
|
96
|
+
continue
|
|
97
|
+
if where is not None and not where(key, value): # type: ignore[call-arg, arg-type]
|
|
98
|
+
continue
|
|
99
|
+
yield _mapping_item(key, value, mode)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
for item in obj:
|
|
103
|
+
if compact and item is None:
|
|
104
|
+
continue
|
|
105
|
+
if where is not None and not where(item): # type: ignore[call-arg, arg-type]
|
|
106
|
+
continue
|
|
107
|
+
yield item
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def aiterator(
|
|
111
|
+
obj: AsyncIterable[Any] | Iterable[Any] | Mapping[Any, Any] | None = None,
|
|
112
|
+
*,
|
|
113
|
+
mode: IteratorMode = "keys",
|
|
114
|
+
where: AsyncWhereSeq | AsyncWhereMap | None = None,
|
|
115
|
+
compact: bool = False,
|
|
116
|
+
) -> AsyncIterator[Any]:
|
|
117
|
+
"""
|
|
118
|
+
Async counterpart to ``iterator``.
|
|
119
|
+
|
|
120
|
+
Accepts async iterables, sync iterables, and mappings. ``where`` may be
|
|
121
|
+
sync or async; mapping predicates still receive ``(key, value)``. Use with
|
|
122
|
+
``async for``.
|
|
123
|
+
"""
|
|
124
|
+
if obj is None:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
async def _allow_mapping(_key: Any, _value: Any) -> bool:
|
|
128
|
+
if where is None:
|
|
129
|
+
return True
|
|
130
|
+
result = where(_key, _value) # type: ignore[call-arg, arg-type]
|
|
131
|
+
if inspect.isawaitable(result):
|
|
132
|
+
result = await result
|
|
133
|
+
return bool(result)
|
|
134
|
+
|
|
135
|
+
async def _allow_item(_item: Any) -> bool:
|
|
136
|
+
if where is None:
|
|
137
|
+
return True
|
|
138
|
+
result = where(_item) # type: ignore[call-arg, arg-type]
|
|
139
|
+
if inspect.isawaitable(result):
|
|
140
|
+
result = await result
|
|
141
|
+
return bool(result)
|
|
142
|
+
|
|
143
|
+
if isinstance(obj, Mapping):
|
|
144
|
+
for key, value in obj.items():
|
|
145
|
+
if compact and value is None:
|
|
146
|
+
continue
|
|
147
|
+
if not await _allow_mapping(key, value):
|
|
148
|
+
continue
|
|
149
|
+
yield _mapping_item(key, value, mode)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if isinstance(obj, AsyncIterable):
|
|
153
|
+
async for item in obj:
|
|
154
|
+
if compact and item is None:
|
|
155
|
+
continue
|
|
156
|
+
if not await _allow_item(item):
|
|
157
|
+
continue
|
|
158
|
+
yield item
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
for item in obj:
|
|
162
|
+
if compact and item is None:
|
|
163
|
+
continue
|
|
164
|
+
if not await _allow_item(item):
|
|
165
|
+
continue
|
|
166
|
+
yield item
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def kv_string(key: str, value: Any, sep: str = ": ", prefix: str = "") -> str:
|
|
170
|
+
"""
|
|
171
|
+
Format a key-value pair into a string.
|
|
172
|
+
"""
|
|
173
|
+
return f"{prefix}{key}{sep}{value}"
|
|
174
|
+
|
|
175
|
+
|
|
43
176
|
class MultiMatch(dict[str, Any]):
|
|
44
177
|
"""
|
|
45
178
|
The result of a path step that matched more than one key.
|
corekit/utils/parsers.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flexible parsers with caller-controlled fallbacks.
|
|
3
|
+
|
|
4
|
+
Each helper attempts to extract the target type and returns a ``ParsedValue``
|
|
5
|
+
carrying both the result and a ``success`` flag. On failure the ``value`` is
|
|
6
|
+
whatever ``default`` the caller supplied (``None`` unless overridden).
|
|
7
|
+
|
|
8
|
+
These are the extraction layer. For a guaranteed target type with that type's
|
|
9
|
+
empty value as the fallback, use the ``safe_*`` wrappers in
|
|
10
|
+
``corekit.utils.coercion``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from datetime import date, datetime
|
|
17
|
+
from typing import Any, NamedTuple
|
|
18
|
+
|
|
19
|
+
from corekit.utils.text import list_to_english
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"ParsedValue",
|
|
23
|
+
"parse_dict",
|
|
24
|
+
"parse_float",
|
|
25
|
+
"parse_int",
|
|
26
|
+
"parse_list",
|
|
27
|
+
"parse_set",
|
|
28
|
+
"parse_string",
|
|
29
|
+
"parse_tuple",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ParsedValue(NamedTuple):
|
|
34
|
+
"""Outcome of a parse attempt."""
|
|
35
|
+
|
|
36
|
+
value: Any
|
|
37
|
+
success: bool
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def ok(cls, value: Any) -> ParsedValue:
|
|
41
|
+
return cls(value=value, success=True)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def error(cls, value: Any) -> ParsedValue:
|
|
45
|
+
return cls(value=value, success=False)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_string(
|
|
49
|
+
item: Any,
|
|
50
|
+
*,
|
|
51
|
+
pretty: bool = False,
|
|
52
|
+
strip: bool = True,
|
|
53
|
+
default: Any = None,
|
|
54
|
+
) -> ParsedValue:
|
|
55
|
+
"""
|
|
56
|
+
Extract a string rendering of ``item``, or ``default`` if there is none.
|
|
57
|
+
|
|
58
|
+
Collections become JSON by default. With ``pretty`` they become an English
|
|
59
|
+
list instead -- ``"a, b, c"``, or ``"key: value, ..."`` for a mapping --
|
|
60
|
+
which reads better in a notification than raw JSON.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def _clean(text: str) -> str:
|
|
64
|
+
return text.strip() if strip else text
|
|
65
|
+
|
|
66
|
+
def _parse_collection(collection: Any) -> str:
|
|
67
|
+
if pretty:
|
|
68
|
+
if isinstance(collection, dict):
|
|
69
|
+
return list_to_english([f"{key}: {value}" for key, value in collection.items()])
|
|
70
|
+
return list_to_english(
|
|
71
|
+
[parse_string(element, pretty=pretty, strip=strip, default="").value for element in collection]
|
|
72
|
+
)
|
|
73
|
+
return json.dumps(collection)
|
|
74
|
+
|
|
75
|
+
if isinstance(item, bytes):
|
|
76
|
+
item = item.decode("utf-8", errors="ignore")
|
|
77
|
+
if isinstance(item, str):
|
|
78
|
+
return ParsedValue.ok(_clean(item))
|
|
79
|
+
# bool is a subclass of int, so both land here and render as "True"/"1".
|
|
80
|
+
if isinstance(item, (int, float)):
|
|
81
|
+
return ParsedValue.ok(str(item))
|
|
82
|
+
if isinstance(item, (list, tuple, set, dict)):
|
|
83
|
+
return ParsedValue.ok(_parse_collection(item))
|
|
84
|
+
if isinstance(item, (date, datetime)):
|
|
85
|
+
return ParsedValue.ok(item.isoformat())
|
|
86
|
+
return ParsedValue.error(default)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def parse_int(item: Any, *, default: Any = None) -> ParsedValue:
|
|
90
|
+
"""
|
|
91
|
+
Extract an int from ``item``, or ``default`` if it will not convert.
|
|
92
|
+
"""
|
|
93
|
+
if isinstance(item, bool):
|
|
94
|
+
return ParsedValue.ok(int(item))
|
|
95
|
+
if isinstance(item, int):
|
|
96
|
+
return ParsedValue.ok(item)
|
|
97
|
+
if isinstance(item, bytes):
|
|
98
|
+
item = item.decode("utf-8", errors="ignore")
|
|
99
|
+
if isinstance(item, (float, str)):
|
|
100
|
+
try:
|
|
101
|
+
return ParsedValue.ok(int(item))
|
|
102
|
+
except (TypeError, ValueError):
|
|
103
|
+
return ParsedValue.error(default)
|
|
104
|
+
return ParsedValue.error(default)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def parse_float(item: Any, *, default: Any = None) -> ParsedValue:
|
|
108
|
+
"""
|
|
109
|
+
Extract a float from ``item``, or ``default`` if it will not convert.
|
|
110
|
+
"""
|
|
111
|
+
if isinstance(item, bool):
|
|
112
|
+
return ParsedValue.ok(float(item))
|
|
113
|
+
if isinstance(item, float):
|
|
114
|
+
return ParsedValue.ok(item)
|
|
115
|
+
if isinstance(item, bytes):
|
|
116
|
+
item = item.decode("utf-8", errors="ignore")
|
|
117
|
+
if isinstance(item, (int, str)):
|
|
118
|
+
try:
|
|
119
|
+
return ParsedValue.ok(float(item))
|
|
120
|
+
except (TypeError, ValueError):
|
|
121
|
+
return ParsedValue.error(default)
|
|
122
|
+
return ParsedValue.error(default)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def parse_list(item: Any, *, default: Any = None) -> ParsedValue:
|
|
126
|
+
"""
|
|
127
|
+
Extract a list from a collection, or ``default`` for anything else.
|
|
128
|
+
|
|
129
|
+
A mapping yields its values. A string is *not* treated as a collection --
|
|
130
|
+
exploding it into characters is almost never what the caller meant.
|
|
131
|
+
"""
|
|
132
|
+
if isinstance(item, list):
|
|
133
|
+
return ParsedValue.ok(item)
|
|
134
|
+
if isinstance(item, (tuple, set)):
|
|
135
|
+
return ParsedValue.ok(list(item))
|
|
136
|
+
if isinstance(item, dict):
|
|
137
|
+
return ParsedValue.ok(list(item.values()))
|
|
138
|
+
return ParsedValue.error(default)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def parse_tuple(item: Any, *, default: Any = None) -> ParsedValue:
|
|
142
|
+
"""
|
|
143
|
+
Extract a tuple from a collection, or ``default`` for anything else.
|
|
144
|
+
|
|
145
|
+
A mapping yields its values. A string is *not* treated as a collection --
|
|
146
|
+
exploding it into characters is almost never what the caller meant.
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(item, tuple):
|
|
149
|
+
return ParsedValue.ok(item)
|
|
150
|
+
if isinstance(item, (list, set)):
|
|
151
|
+
return ParsedValue.ok(tuple(item))
|
|
152
|
+
if isinstance(item, dict):
|
|
153
|
+
return ParsedValue.ok(tuple(item.values()))
|
|
154
|
+
return ParsedValue.error(default)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def parse_set(item: Any, *, default: Any = None) -> ParsedValue:
|
|
158
|
+
"""
|
|
159
|
+
Extract a set from a collection, or ``default`` for anything else.
|
|
160
|
+
|
|
161
|
+
A mapping yields its values. A string is *not* treated as a collection --
|
|
162
|
+
exploding it into characters is almost never what the caller meant.
|
|
163
|
+
Unhashable elements fail the conversion and yield ``default``.
|
|
164
|
+
"""
|
|
165
|
+
if isinstance(item, set):
|
|
166
|
+
return ParsedValue.ok(item)
|
|
167
|
+
if isinstance(item, (list, tuple)):
|
|
168
|
+
try:
|
|
169
|
+
return ParsedValue.ok(set(item))
|
|
170
|
+
except TypeError:
|
|
171
|
+
return ParsedValue.error(default)
|
|
172
|
+
if isinstance(item, dict):
|
|
173
|
+
try:
|
|
174
|
+
return ParsedValue.ok(set(item.values()))
|
|
175
|
+
except TypeError:
|
|
176
|
+
return ParsedValue.error(default)
|
|
177
|
+
return ParsedValue.error(default)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def parse_dict(item: Any, *, default: Any = None) -> ParsedValue:
|
|
181
|
+
"""
|
|
182
|
+
Extract a mapping from ``item``, or ``default`` if none is available.
|
|
183
|
+
|
|
184
|
+
Accepts a ``dict`` directly, then common dump protocols (``to_dict``,
|
|
185
|
+
``model_dump``, ``dict``), then a plain ``__dict__``.
|
|
186
|
+
"""
|
|
187
|
+
if isinstance(item, dict):
|
|
188
|
+
return ParsedValue.ok(item)
|
|
189
|
+
|
|
190
|
+
for method_name in ("to_dict", "model_dump", "dict"):
|
|
191
|
+
method = getattr(item, method_name, None)
|
|
192
|
+
if not callable(method):
|
|
193
|
+
continue
|
|
194
|
+
try:
|
|
195
|
+
result = method()
|
|
196
|
+
except Exception:
|
|
197
|
+
continue
|
|
198
|
+
if isinstance(result, dict):
|
|
199
|
+
return ParsedValue.ok(result)
|
|
200
|
+
|
|
201
|
+
namespace = getattr(item, "__dict__", None)
|
|
202
|
+
if isinstance(namespace, dict):
|
|
203
|
+
return ParsedValue.ok(namespace)
|
|
204
|
+
|
|
205
|
+
return ParsedValue.error(default)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-corekit
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Shared foundations for Python projects: logging, benchmarking, registries, FastAPI application and routers, SQL statements and migrations, background tasks, ETL,
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Shared foundations for Python projects: logging, benchmarking, registries, FastAPI application and routers, SQL statements and migrations, background tasks, ETL, LLM chat/tool primitives, and HTML scraping
|
|
5
5
|
Author: Steven Jacobsen
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/stevejaker/corekit
|
|
@@ -23,6 +23,7 @@ Requires-Dist: sqlmodel<0.1,>=0.0.16
|
|
|
23
23
|
Requires-Dist: SQLAlchemy<3,>=2.0
|
|
24
24
|
Requires-Dist: redis<7,>=5.0
|
|
25
25
|
Requires-Dist: httpx<1,>=0.27
|
|
26
|
+
Requires-Dist: selectolax<1,>=0.3
|
|
26
27
|
Requires-Dist: docker<8,>=7.0
|
|
27
28
|
Requires-Dist: PyYAML<7,>=6.0
|
|
28
29
|
Requires-Dist: dill<0.5,>=0.3.8
|
|
@@ -347,13 +348,13 @@ class GithubClient(BaseApiClient):
|
|
|
347
348
|
return "https://api.github.com"
|
|
348
349
|
|
|
349
350
|
response = GithubClient().get("/users/octocat")
|
|
350
|
-
response.data
|
|
351
|
+
response.data.login
|
|
351
352
|
```
|
|
352
353
|
|
|
353
|
-
Retries 429 and 5xx with exponential backoff.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
`await client.get(...)`.
|
|
354
|
+
Retries 429 and 5xx with exponential backoff. ``BaseHttpClient`` returns a
|
|
355
|
+
``BaseHttpResponse`` (text + metadata); ``BaseApiClient`` adds structured
|
|
356
|
+
``data`` from JSON (``None`` when the body is not JSON). The same methods are
|
|
357
|
+
awaitable inside a running event loop: `await client.get(...)`.
|
|
357
358
|
|
|
358
359
|
## Serialization
|
|
359
360
|
|
|
@@ -22,7 +22,7 @@ corekit/connections/registry.py,sha256=BR04dGthcGCtON2MxNsl-TlhRgbRlvGj0RLymDFBk
|
|
|
22
22
|
corekit/connections/redis/__init__.py,sha256=BsWLZNqpKAqIoreHGLEtftnMqVw-ESK8jbexDAughag,174
|
|
23
23
|
corekit/connections/redis/connection.py,sha256=H6r5WsDfXFgIQlp6vq3rKjp-oBFQoE5h_YaJXZEvHIs,9850
|
|
24
24
|
corekit/connections/sql/__init__.py,sha256=WRBcQ9draDQSooYi4k4peWyuRTgSyEk_WwOYYOAN9K4,882
|
|
25
|
-
corekit/connections/sql/connection.py,sha256=
|
|
25
|
+
corekit/connections/sql/connection.py,sha256=1ieLXssO2lyFZD9Uf_5wyM5TJmdEqYl8RzR2fMTJTlg,12410
|
|
26
26
|
corekit/connections/sql/query.py,sha256=8s5atIssmN2l9FCL2IHnIQqwxOk0fA_mPNh36koLhoE,233
|
|
27
27
|
corekit/connections/sql/table.py,sha256=C7_QCRPd7C1JeH7aIByUBVWsx5qiEOFavWOo6c72djM,3125
|
|
28
28
|
corekit/connections/sql/fields/__init__.py,sha256=2I-TCsvi321yQhesKNZgY1T77FtATZ6o7pw0MNHtbAo,184
|
|
@@ -94,12 +94,19 @@ corekit/files/base.py,sha256=Ca2bhdlwntHh425PedaUG-UW2fEFZsPCss_3f7REsAE,4022
|
|
|
94
94
|
corekit/files/enum.py,sha256=mcrQXcp-AizTAp4VoaVMLj9zu5D81XXMsnZwZqe6fz0,597
|
|
95
95
|
corekit/files/json.py,sha256=pRYLKNjeVmkDQsgtjBboFFY5Ljx1Pwa3MFEeQBc4o-g,909
|
|
96
96
|
corekit/files/toml.py,sha256=YFRwghMapbCRSl7srqcI7IhqaTQDyO56XRasGMu_Aug,1273
|
|
97
|
-
corekit/
|
|
98
|
-
corekit/
|
|
99
|
-
corekit/
|
|
97
|
+
corekit/html/__init__.py,sha256=25xsrlhaWREDuGpWc6xnN-Mbir7AsCnzjjQODJ58XVo,535
|
|
98
|
+
corekit/html/client.py,sha256=59eTlvJh9QFiPQ7HFHEhju9UkoEpS5YafinxFI5w7Q0,664
|
|
99
|
+
corekit/html/models.py,sha256=zxN3mSAuTQ0GE27Gxa3ptcp_EAmm5tlBUzwMovDevLU,357
|
|
100
|
+
corekit/html/parser.py,sha256=blSEBl5clbx_dOBPKu0S45_MiRjLSa7LeGpGc8opdWA,10300
|
|
101
|
+
corekit/html/scraper.py,sha256=af3UHf8LZuE4tEYr_MHo-h9FF6b8PQUtoTmlzSFR3FE,4366
|
|
102
|
+
corekit/html/settings.py,sha256=Xalz1Zg9wHyPhlzfIwsFE23aOE6woShHJLNEHg4mCWE,6951
|
|
103
|
+
corekit/html/state.py,sha256=McWmvu7y4qR0M2mSnjPAFtnAow9sv-nWHzolJQlvbyk,1534
|
|
104
|
+
corekit/http/__init__.py,sha256=S3FlvpBjYoLrFaMY7OTQLMM6d7etaDRJP53_4aPuKZk,1723
|
|
105
|
+
corekit/http/api.py,sha256=WiBfv3tTifxUuvoj3rGqZxCPm2L8z3N_F7hvmLuzprM,3027
|
|
106
|
+
corekit/http/client.py,sha256=CONO9z5CGVh5H99yCYnNjPMUYe8G8FFzbeBhusLbBSw,7217
|
|
100
107
|
corekit/http/exceptions.py,sha256=etkKN6rgnhENctO7YNvaRAKsctK0PXKCxAe15l6mNwY,4252
|
|
101
108
|
corekit/http/exponential_backoff.py,sha256=5jjGTspX_BC9aBKPr5Y5dNhJqXesmljsjJoQ6i2d4_I,3180
|
|
102
|
-
corekit/http/response.py,sha256=
|
|
109
|
+
corekit/http/response.py,sha256=t_nGIwA-uyuQlJBGMgxbodCNjRnqc1xUcxMsbEXeMBM,3415
|
|
103
110
|
corekit/http/status.py,sha256=rZ-cdliG9EAvWMTzvxjqjipRkZRc6RgSzcBY4Kewdg0,2377
|
|
104
111
|
corekit/http/stream.py,sha256=lkERyCXz5amOWzoE-b12iftf3l_G-ghwSvN1lL5lUio,3257
|
|
105
112
|
corekit/jobs/__init__.py,sha256=vsv8o23ApT6MlOwOq-AFYeQW2NT1kjyvu89A5CL2PsY,880
|
|
@@ -107,13 +114,13 @@ corekit/jobs/registry.py,sha256=cco6884eVuIYHEtSv6P62MICFjX-QhuSZIFkEJcQi_M,2557
|
|
|
107
114
|
corekit/jobs/runner.py,sha256=VyiTrQJT-E8-XuNBh8s1QgLJkKhkpBq_398Fs_03Kow,3321
|
|
108
115
|
corekit/jobs/task.py,sha256=tqr-QaQSHyfPjbyZscM5i-YgT-fl58cpd2FVxevtG_E,6112
|
|
109
116
|
corekit/llm/__init__.py,sha256=Qa-5V2cG4aH98b0nLY2tMx0G7uhoWhcaaZ7JY1dJjcA,3217
|
|
110
|
-
corekit/llm/client.py,sha256=
|
|
117
|
+
corekit/llm/client.py,sha256=X-dkizlps-ndhDzcImBfLDDWaoPdwMin7bf58QU2fdQ,6519
|
|
111
118
|
corekit/llm/enum.py,sha256=WyikZcEt17Rnj5jFS6o2ENXIdBBFB-NFjdCKDVtIToc,2582
|
|
112
119
|
corekit/llm/events.py,sha256=gEj3HpXLndCoPLF3C5jn-Gv5N1K6EPosR7bc5DaHwz4,2202
|
|
113
120
|
corekit/llm/messages.py,sha256=KomU69Kjfgif0fMXrbEr9VsQ_RpcFjgsE2S0ThC2SIA,5070
|
|
114
121
|
corekit/llm/protocols.py,sha256=ahCZwR3vQiB6LqaarTBGc5VSbe5mtb9vDLoLcOTmL_k,1770
|
|
115
122
|
corekit/llm/streaming.py,sha256=fiQdJMHDrMBSF5hDMCqcQ3WulrOyoQ67N-c3Chb00Sg,4319
|
|
116
|
-
corekit/llm/wire.py,sha256=
|
|
123
|
+
corekit/llm/wire.py,sha256=GbK7nF27YryNIDe1uOG_3RR-er3BFvULhVy73DiGgE0,6308
|
|
117
124
|
corekit/llm/prompts/__init__.py,sha256=9z8UwMqkh11MNcrom8hNZjugXq1MVF_EpPkRfF72zNA,544
|
|
118
125
|
corekit/llm/prompts/enum.py,sha256=OYTd-riG70HiGoBVxJpA1_FttyGR-ITsEojHfHjDKdk,1219
|
|
119
126
|
corekit/llm/prompts/exceptions.py,sha256=BX7VvngByXfSf3UGKAnFAPOytJrYukdEN_gVKYIfypc,546
|
|
@@ -122,7 +129,7 @@ corekit/llm/prompts/template.py,sha256=dgKje-v4R3JN3SPsDBjGm4zdohEsGRTgpbv1bvaOT
|
|
|
122
129
|
corekit/llm/tools/__init__.py,sha256=3hoiam1oatawk9k8-pcY63JoywiOh3EoM7TvkW00nMY,527
|
|
123
130
|
corekit/llm/tools/base.py,sha256=Mby2M3WA4nKgkmOpQwJWvKHGXlaZU62EGlHQUxsL_wk,3309
|
|
124
131
|
corekit/llm/tools/detection.py,sha256=R3jMxJwOdMzyTo9DISi_yYx1zEHUJJ6AN61bHkvO3zM,3292
|
|
125
|
-
corekit/llm/tools/loop.py,sha256=
|
|
132
|
+
corekit/llm/tools/loop.py,sha256=x7zlfYgeiXbtgna3IIczKkh5PK9fY2SfUpBOle21Dtw,9381
|
|
126
133
|
corekit/llm/tools/registry.py,sha256=dkIucONGsmaw-cgTNIIV2WDVGAF45twOHrvTvavs4X4,3362
|
|
127
134
|
corekit/log_monitor/__init__.py,sha256=uuXTQdI8EMeFbdt69G-2K8dKh5Nt-h9SzBEsOGcu0lo,371
|
|
128
135
|
corekit/log_monitor/constants.py,sha256=8nzIzU3Qit9h7_stZLWW1Q_0DTnYglw_j5bQUsF586g,230
|
|
@@ -142,13 +149,13 @@ corekit/observability/timing/timer.py,sha256=rBGYYnBq-1E85Om37dkbPM9wKsXsltyeWeA
|
|
|
142
149
|
corekit/registry/__init__.py,sha256=R67Jssr06Eyx2u5AbX1omm2LziWvwS_Sd7fExnNv9CM,681
|
|
143
150
|
corekit/registry/ordered.py,sha256=_KS4mWzmLgFvODW2M0_ugxZIBW_dKhwwxrVrtNqLMfs,2506
|
|
144
151
|
corekit/registry/registry.py,sha256=t1iwzxgvd-iyLIf_5wahg_P7eN9V1oumpupkAqi2GdQ,5601
|
|
145
|
-
corekit/schemas/__init__.py,sha256=
|
|
152
|
+
corekit/schemas/__init__.py,sha256=4EK7QNWP16U6ZKno_ZKTDfgSflc9W2PFMPskc0kvSdw,470
|
|
146
153
|
corekit/schemas/enum.py,sha256=gLc44kIXRfpDFkV51JoN4KT1CU92hnb529rd1b88fBM,2033
|
|
147
154
|
corekit/schemas/types.py,sha256=AWIxZHXUiUjJRZjmOhAdCq3RIsRzL-Fy7RvYy4i5Ozo,1347
|
|
148
155
|
corekit/schemas/version.py,sha256=InWtfKf0iyfu_eBLaFm_QNwkSjpgKc1jGJZeWYjQQUc,1753
|
|
149
156
|
corekit/schemas/dataclasses/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
150
157
|
corekit/schemas/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
151
|
-
corekit/schemas/models/arbitrary.py,sha256=
|
|
158
|
+
corekit/schemas/models/arbitrary.py,sha256=ik8V9Pm2X3eKv1vUp3IF5v3c6gDqoyisSBnen63-Pcc,1681
|
|
152
159
|
corekit/schemas/models/date_models.py,sha256=UOlSLVsWqgr_jnHfxVRgSl4nVVdfbe6zPBYveOgRcIg,399
|
|
153
160
|
corekit/schemas/pydantic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
154
161
|
corekit/schemas/pydantic/fields.py,sha256=ScP2ZQCR2qTWaqwjKl7lGvkTGSIeQBJ0yeNs79E47AA,1175
|
|
@@ -157,18 +164,19 @@ corekit/serialization/enum.py,sha256=pzHuAIXeonEF7-jyN1uYddJk-xvmQGuymDf1U6RWR5w
|
|
|
157
164
|
corekit/serialization/pickle_file.py,sha256=dudKIxR8ueT3wx_IBxHJeoqnFeP1rT7dlTb_PXlSkd8,2251
|
|
158
165
|
corekit/serialization/serializable.py,sha256=viF3ZCKRJ90hk6m4lbqIfo6ifsY2c4sXr4xZrn1Yz-Q,1999
|
|
159
166
|
corekit/serialization/serializer.py,sha256=XlGE1Rq8M55Jl2a4F5MkvDcba2VI7lrh0v9kwL0DWG8,6617
|
|
160
|
-
corekit/utils/__init__.py,sha256=
|
|
161
|
-
corekit/utils/coercion.py,sha256=
|
|
162
|
-
corekit/utils/collections.py,sha256=
|
|
167
|
+
corekit/utils/__init__.py,sha256=lNdmVmQBUFckkCIjzxYLHpLe06rudXS9t_evVEv-le4,2419
|
|
168
|
+
corekit/utils/coercion.py,sha256=lnAdPuY7Hha81of1I8oPIu6C8ow6JqWWpuF1sPX17Ek,3084
|
|
169
|
+
corekit/utils/collections.py,sha256=4ieN3r44Y1DPoJQYpoXn8mL_nTKd5i9GjAoBOMXwd40,8335
|
|
163
170
|
corekit/utils/ids.py,sha256=BVniaYqewM-EosDxcJAT_S_7T9C1c5X8nsaVGs5up34,1731
|
|
171
|
+
corekit/utils/parsers.py,sha256=b7bCaMd7_VKaFnin6vn3v1Eol_Z0vvr3vjDO4ro5ycI,6628
|
|
164
172
|
corekit/utils/payload.py,sha256=T7EgdVU0S5h8MAzvQsmkfRPMatWqUbxjHL9hmZdM9ho,3882
|
|
165
173
|
corekit/utils/raise_exc.py,sha256=cz7ljc6ZW90AGQussKuANN8PLsDLUhl-E8FZ0Ipsw-0,175
|
|
166
174
|
corekit/utils/text.py,sha256=0322DdZGuDGEagTyejuda6QT1jbbxNIO6loC-_AGml0,2311
|
|
167
175
|
corekit/utils/time.py,sha256=-iPaETXcgDu7J39Kwe938JyZKhPVjl6a572BBqsqcDQ,2215
|
|
168
176
|
corekit/utils/validators.py,sha256=_HVKo3y264KGNaZ-1RyM1x46VPvu7XKcQkrHWDC9uxs,340
|
|
169
177
|
corekit/utils/void.py,sha256=eXDb2QynuguiSgHGJLdgE08MuRnxBdYebRdMD7s1m9o,178
|
|
170
|
-
python_corekit-0.
|
|
171
|
-
python_corekit-0.
|
|
172
|
-
python_corekit-0.
|
|
173
|
-
python_corekit-0.
|
|
174
|
-
python_corekit-0.
|
|
178
|
+
python_corekit-0.5.0.dist-info/licenses/LICENSE,sha256=357LYxbxAQZ95q5cV-8JK6j-KNgiE6AteRodjEsm-kE,1072
|
|
179
|
+
python_corekit-0.5.0.dist-info/METADATA,sha256=GFJChQrVckK79FiPnCddTLGr36xpxik6BqrtAjskE_A,15185
|
|
180
|
+
python_corekit-0.5.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
181
|
+
python_corekit-0.5.0.dist-info/top_level.txt,sha256=SDK4o8BoaI47E9tPbiNCbbWoc2y6N_wCjwnIEfr-GWI,8
|
|
182
|
+
python_corekit-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|