thds.core 0.0.1__py3-none-any.whl → 1.31.20250116223856__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.
Potentially problematic release.
This version of thds.core might be problematic. Click here for more details.
- thds/core/__init__.py +48 -0
- thds/core/ansi_esc.py +46 -0
- thds/core/cache.py +201 -0
- thds/core/calgitver.py +82 -0
- thds/core/concurrency.py +100 -0
- thds/core/config.py +250 -0
- thds/core/decos.py +55 -0
- thds/core/dict_utils.py +188 -0
- thds/core/env.py +40 -0
- thds/core/exit_after.py +121 -0
- thds/core/files.py +125 -0
- thds/core/fretry.py +115 -0
- thds/core/generators.py +56 -0
- thds/core/git.py +81 -0
- thds/core/hash_cache.py +86 -0
- thds/core/hashing.py +106 -0
- thds/core/home.py +15 -0
- thds/core/hostname.py +10 -0
- thds/core/imports.py +17 -0
- thds/core/inspect.py +58 -0
- thds/core/iterators.py +9 -0
- thds/core/lazy.py +83 -0
- thds/core/link.py +153 -0
- thds/core/log/__init__.py +29 -0
- thds/core/log/basic_config.py +171 -0
- thds/core/log/json_formatter.py +43 -0
- thds/core/log/kw_formatter.py +84 -0
- thds/core/log/kw_logger.py +93 -0
- thds/core/log/logfmt.py +302 -0
- thds/core/merge_args.py +168 -0
- thds/core/meta.json +8 -0
- thds/core/meta.py +518 -0
- thds/core/parallel.py +200 -0
- thds/core/pickle_visit.py +24 -0
- thds/core/prof.py +276 -0
- thds/core/progress.py +112 -0
- thds/core/protocols.py +17 -0
- thds/core/py.typed +0 -0
- thds/core/scaling.py +39 -0
- thds/core/scope.py +199 -0
- thds/core/source.py +238 -0
- thds/core/source_serde.py +104 -0
- thds/core/sqlite/__init__.py +21 -0
- thds/core/sqlite/connect.py +33 -0
- thds/core/sqlite/copy.py +35 -0
- thds/core/sqlite/ddl.py +4 -0
- thds/core/sqlite/functions.py +63 -0
- thds/core/sqlite/index.py +22 -0
- thds/core/sqlite/insert_utils.py +23 -0
- thds/core/sqlite/merge.py +84 -0
- thds/core/sqlite/meta.py +190 -0
- thds/core/sqlite/read.py +66 -0
- thds/core/sqlite/sqlmap.py +179 -0
- thds/core/sqlite/structured.py +138 -0
- thds/core/sqlite/types.py +64 -0
- thds/core/sqlite/upsert.py +139 -0
- thds/core/sqlite/write.py +99 -0
- thds/core/stack_context.py +41 -0
- thds/core/thunks.py +40 -0
- thds/core/timer.py +214 -0
- thds/core/tmp.py +85 -0
- thds/core/types.py +4 -0
- thds.core-1.31.20250116223856.dist-info/METADATA +68 -0
- thds.core-1.31.20250116223856.dist-info/RECORD +67 -0
- {thds.core-0.0.1.dist-info → thds.core-1.31.20250116223856.dist-info}/WHEEL +1 -1
- thds.core-1.31.20250116223856.dist-info/entry_points.txt +4 -0
- thds.core-1.31.20250116223856.dist-info/top_level.txt +1 -0
- thds.core-0.0.1.dist-info/METADATA +0 -8
- thds.core-0.0.1.dist-info/RECORD +0 -4
- thds.core-0.0.1.dist-info/top_level.txt +0 -1
thds/core/timer.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module that adds a simple interface for tracking execution times of various pieces of code.
|
|
3
|
+
|
|
4
|
+
```python
|
|
5
|
+
tracker = TimeTracker()
|
|
6
|
+
|
|
7
|
+
@tracker.track()
|
|
8
|
+
def computation(num: int):
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
with tracker(tracker.total):
|
|
12
|
+
for i in range(10):
|
|
13
|
+
with tracker("setup"):
|
|
14
|
+
...
|
|
15
|
+
...
|
|
16
|
+
computation(i)
|
|
17
|
+
|
|
18
|
+
print(tracker.percentage_of_totals)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`TimeTracker`s can also be merged.
|
|
22
|
+
|
|
23
|
+
*module_1.py*
|
|
24
|
+
```python
|
|
25
|
+
|
|
26
|
+
tracker = TimeTracker()
|
|
27
|
+
|
|
28
|
+
@tracker.track()
|
|
29
|
+
def foo():
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
@tracker.track()
|
|
33
|
+
def bar():
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@tracker.track()
|
|
38
|
+
def main():
|
|
39
|
+
foo()
|
|
40
|
+
bar()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
*module_2.py*
|
|
44
|
+
```python
|
|
45
|
+
import time
|
|
46
|
+
import module_1 as mod1
|
|
47
|
+
|
|
48
|
+
tracker = TimeTracker()
|
|
49
|
+
|
|
50
|
+
with tracker(tracker.total):
|
|
51
|
+
for i in range(100):
|
|
52
|
+
time.sleep(5)
|
|
53
|
+
mod1.main()
|
|
54
|
+
|
|
55
|
+
tracker.merge(mod1.tracker)
|
|
56
|
+
```
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
import json
|
|
60
|
+
import time
|
|
61
|
+
from collections import defaultdict
|
|
62
|
+
from dataclasses import asdict, dataclass
|
|
63
|
+
from functools import wraps
|
|
64
|
+
from typing import (
|
|
65
|
+
Callable,
|
|
66
|
+
DefaultDict,
|
|
67
|
+
Dict,
|
|
68
|
+
Generator,
|
|
69
|
+
Iterator,
|
|
70
|
+
List,
|
|
71
|
+
Optional,
|
|
72
|
+
Tuple,
|
|
73
|
+
TypeVar,
|
|
74
|
+
Union,
|
|
75
|
+
cast,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
from typing_extensions import ParamSpec
|
|
79
|
+
|
|
80
|
+
from thds.core import config, log
|
|
81
|
+
|
|
82
|
+
T = TypeVar("T")
|
|
83
|
+
P = ParamSpec("P")
|
|
84
|
+
|
|
85
|
+
F = TypeVar("F", bound=Callable)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
SINGLE_LINE_JSON_TIMERS = config.item(
|
|
89
|
+
"unified.single-line-json-timers", default=False, parse=config.tobool
|
|
90
|
+
)
|
|
91
|
+
# setting this to true makes it much easier to extract the data from logs in a production run using Grafana.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def timer(func: F) -> F:
|
|
95
|
+
"""
|
|
96
|
+
Decorator to add logging of timer information to a function invocation. Logs when entering a function and then logs
|
|
97
|
+
with time information when exiting.
|
|
98
|
+
:param func:
|
|
99
|
+
Function to decorate with timing info.
|
|
100
|
+
:return:
|
|
101
|
+
Wrapped function.
|
|
102
|
+
"""
|
|
103
|
+
logger = log.getLogger(func.__module__)
|
|
104
|
+
|
|
105
|
+
@wraps(func)
|
|
106
|
+
def wrapper_timer(*args, **kwargs):
|
|
107
|
+
start = time.time()
|
|
108
|
+
start_formatted = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime(start))
|
|
109
|
+
|
|
110
|
+
logger.info("Starting %r at %s", func.__name__, start_formatted)
|
|
111
|
+
start_time = time.perf_counter()
|
|
112
|
+
value = func(*args, **kwargs)
|
|
113
|
+
end_time = time.perf_counter()
|
|
114
|
+
run_time = end_time - start_time
|
|
115
|
+
|
|
116
|
+
end = time.time()
|
|
117
|
+
end_formatted = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime(end))
|
|
118
|
+
logger.info("Finished %r in %0.4f secs at %s", func.__name__, run_time, end_formatted)
|
|
119
|
+
return value
|
|
120
|
+
|
|
121
|
+
return cast(F, wrapper_timer)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class Timer:
|
|
126
|
+
secs: float = 0.0
|
|
127
|
+
calls: int = 0
|
|
128
|
+
|
|
129
|
+
def __iter__(self) -> Generator[Tuple[str, Union[int, float]], None, None]:
|
|
130
|
+
for k, v in {**asdict(self), "secs_per_call": self.secs_per_call}.items():
|
|
131
|
+
yield k, v
|
|
132
|
+
|
|
133
|
+
def __add__(self, other: "Timer") -> "Timer":
|
|
134
|
+
return Timer(self.secs + other.secs, self.calls + other.calls)
|
|
135
|
+
|
|
136
|
+
def __radd__(self, other):
|
|
137
|
+
if other == 0:
|
|
138
|
+
return self
|
|
139
|
+
else:
|
|
140
|
+
return self.__add__(other)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def secs_per_call(self) -> float:
|
|
144
|
+
return (self.secs / self.calls) / 60.0 if self.calls > 0 else float("nan")
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def mins(self) -> float:
|
|
148
|
+
return self.secs / 60.0
|
|
149
|
+
|
|
150
|
+
def pct_of_total(self, total: float) -> float:
|
|
151
|
+
return (self.secs / total) * 100
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TimeTracker:
|
|
155
|
+
total: str = "total"
|
|
156
|
+
|
|
157
|
+
def __init__(self, times: Optional[DefaultDict[str, Timer]] = None):
|
|
158
|
+
self.tracked_times: DefaultDict[str, Timer] = times or defaultdict(Timer)
|
|
159
|
+
self._names: List[str] = []
|
|
160
|
+
self._start_times: List[float] = []
|
|
161
|
+
|
|
162
|
+
def reset(self) -> None:
|
|
163
|
+
self.tracked_times = defaultdict(Timer)
|
|
164
|
+
self._names = []
|
|
165
|
+
self._start_times = []
|
|
166
|
+
|
|
167
|
+
def to_json(self) -> Iterator[str]:
|
|
168
|
+
if SINGLE_LINE_JSON_TIMERS:
|
|
169
|
+
for name, timer in sorted(self.tracked_times.items(), key=lambda x: x[0]):
|
|
170
|
+
yield json.dumps({name: dict(timer)}, indent=None)
|
|
171
|
+
else:
|
|
172
|
+
yield json.dumps(
|
|
173
|
+
{name: dict(timer) for name, timer in self.tracked_times.items()},
|
|
174
|
+
sort_keys=True,
|
|
175
|
+
indent=4,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def track(self, component_name: Optional[str] = None):
|
|
179
|
+
def decorator(func):
|
|
180
|
+
@wraps(func)
|
|
181
|
+
def wrapper(*args, **kwargs):
|
|
182
|
+
start = time.perf_counter()
|
|
183
|
+
func_result = func(*args, **kwargs)
|
|
184
|
+
cmpnt = self.tracked_times[component_name or f"{func.__module__}.{func.__qualname__}"]
|
|
185
|
+
cmpnt.secs += time.perf_counter() - start
|
|
186
|
+
cmpnt.calls += 1
|
|
187
|
+
return func_result
|
|
188
|
+
|
|
189
|
+
return wrapper
|
|
190
|
+
|
|
191
|
+
return decorator
|
|
192
|
+
|
|
193
|
+
def __call__(self, component_name: str) -> "TimeTracker":
|
|
194
|
+
self._names.append(component_name)
|
|
195
|
+
return self
|
|
196
|
+
|
|
197
|
+
def __enter__(self) -> "TimeTracker":
|
|
198
|
+
self._start_times.append(time.perf_counter())
|
|
199
|
+
return self
|
|
200
|
+
|
|
201
|
+
def __exit__(self, *args, **kwargs):
|
|
202
|
+
cmpnt = self.tracked_times[self._names.pop()]
|
|
203
|
+
cmpnt.secs += time.perf_counter() - self._start_times.pop()
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def pct_of_totals(self) -> Dict[str, float]:
|
|
207
|
+
total = self.tracked_times.get(self.total)
|
|
208
|
+
if total:
|
|
209
|
+
return {
|
|
210
|
+
name: timer.pct_of_total(total.secs)
|
|
211
|
+
for name, timer in self.tracked_times.items()
|
|
212
|
+
if name != "total"
|
|
213
|
+
}
|
|
214
|
+
return {}
|
thds/core/tmp.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Utilities for managing temporary directories and files.
|
|
2
|
+
|
|
3
|
+
Module is given this name partly because we tend not to name other things internally using
|
|
4
|
+
the word 'tmp', preferring instead 'temp'. Therefore `tmp` will be a little less ambiguous
|
|
5
|
+
overall.
|
|
6
|
+
"""
|
|
7
|
+
import contextlib
|
|
8
|
+
import shutil
|
|
9
|
+
import tempfile
|
|
10
|
+
import typing as ty
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from random import SystemRandom
|
|
13
|
+
|
|
14
|
+
from .home import HOMEDIR
|
|
15
|
+
from .types import StrOrPath
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _are_same_fs(path1: Path, path2: Path) -> bool:
|
|
19
|
+
return path1.stat().st_dev == path2.stat().st_dev
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _walk_up_to_existing_dir(path: Path) -> Path:
|
|
23
|
+
existing_dir = path.resolve()
|
|
24
|
+
while not existing_dir.exists() or not existing_dir.is_dir():
|
|
25
|
+
# this is guaranteed to terminate at the FS root
|
|
26
|
+
existing_dir = existing_dir.parent
|
|
27
|
+
if existing_dir.resolve() == existing_dir.parent.resolve():
|
|
28
|
+
raise FileNotFoundError(f"{path} does not exist on a known filesystem.")
|
|
29
|
+
return existing_dir
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@contextlib.contextmanager
|
|
33
|
+
def temppath_same_fs(lcl_path: StrOrPath = "") -> ty.Iterator[Path]:
|
|
34
|
+
"""Builds a temporary Path (with no file at it) on the same filesystem as the
|
|
35
|
+
provided local path. No file or directory is created for you at the actual
|
|
36
|
+
Path.
|
|
37
|
+
|
|
38
|
+
Useful for making sure that you can do atomic moves from tempfiles to your final
|
|
39
|
+
location.
|
|
40
|
+
"""
|
|
41
|
+
parent_dir = _walk_up_to_existing_dir(Path(lcl_path) if lcl_path else HOMEDIR())
|
|
42
|
+
basename = Path(lcl_path).name
|
|
43
|
+
|
|
44
|
+
def _tempdir_same_filesystem() -> ty.Iterator[Path]:
|
|
45
|
+
# we would prefer _not_ to reinvent the wheel here, but if the tempfiles are
|
|
46
|
+
# getting created on a different volume, then moves are no longer atomic, and
|
|
47
|
+
# that's a huge pain for lots of reasons.
|
|
48
|
+
fname_parts = [
|
|
49
|
+
".thds-core-tmp-home-fs",
|
|
50
|
+
str(SystemRandom().random())[2:], # makes a float look like a numeric string
|
|
51
|
+
]
|
|
52
|
+
if basename:
|
|
53
|
+
fname_parts.append(basename)
|
|
54
|
+
dpath = parent_dir / "-".join(fname_parts)
|
|
55
|
+
try:
|
|
56
|
+
yield dpath
|
|
57
|
+
finally:
|
|
58
|
+
if dpath.exists():
|
|
59
|
+
if dpath.is_file():
|
|
60
|
+
dpath.unlink()
|
|
61
|
+
else:
|
|
62
|
+
shutil.rmtree(dpath, ignore_errors=True)
|
|
63
|
+
|
|
64
|
+
with tempfile.TemporaryDirectory() as tdir:
|
|
65
|
+
# actually check whether we're on the same filesystem.
|
|
66
|
+
if _are_same_fs(parent_dir, Path(tdir)):
|
|
67
|
+
yield Path(tdir) / "-".join(filter(None, ["thds-core-tmp-def-fs", basename]))
|
|
68
|
+
else:
|
|
69
|
+
yield from _tempdir_same_filesystem()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@contextlib.contextmanager
|
|
73
|
+
def tempdir_same_fs(lcl_path: StrOrPath = "") -> ty.Iterator[Path]:
|
|
74
|
+
"""Builds a temporary directory on the same filesystem as the
|
|
75
|
+
provided local path.
|
|
76
|
+
|
|
77
|
+
Useful for making sure that you can do atomic moves from tempfiles to your final
|
|
78
|
+
location.
|
|
79
|
+
|
|
80
|
+
If you have no need for the above uses, teh built-in `tempfile.tmpdir` is a functionally
|
|
81
|
+
equivalent substitute.
|
|
82
|
+
"""
|
|
83
|
+
with temppath_same_fs(lcl_path) as tpath:
|
|
84
|
+
tpath.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
yield tpath
|
thds/core/types.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: thds.core
|
|
3
|
+
Version: 1.31.20250116223856
|
|
4
|
+
Summary: Core utilities.
|
|
5
|
+
Author: Trilliant Health
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: attrs>=22.2.0
|
|
8
|
+
Requires-Dist: cattrs>=22.2.0
|
|
9
|
+
Requires-Dist: setuptools
|
|
10
|
+
Requires-Dist: typing-extensions
|
|
11
|
+
|
|
12
|
+
# core Library
|
|
13
|
+
|
|
14
|
+
The monorepo successor to `core`
|
|
15
|
+
|
|
16
|
+
## Development
|
|
17
|
+
|
|
18
|
+
If making changes to the library please add an entry to `CHANGES.md`, and if the change is more than a
|
|
19
|
+
patch, please bump the version in `pyproject.toml` accordingly.
|
|
20
|
+
|
|
21
|
+
## Config
|
|
22
|
+
|
|
23
|
+
`thds.core.config` provides a general-purpose config system designed to regularize how we implement
|
|
24
|
+
configuration both for libraries and applications. Please see its [README here](src/thds/core/CONFIG.md)!
|
|
25
|
+
|
|
26
|
+
## Logging config
|
|
27
|
+
|
|
28
|
+
This library handles configuration of all DS loggers. By default, all INFO-and-above messages are written
|
|
29
|
+
(to `stderr`).
|
|
30
|
+
|
|
31
|
+
### Default output formatter
|
|
32
|
+
|
|
33
|
+
By default we use a custom formatter intended to make things maximally human-readable.
|
|
34
|
+
|
|
35
|
+
If you want structured logs, you might try setting `THDS_CORE_LOG_FORMAT=logfmt`, or `json` if you want
|
|
36
|
+
JSON logs.
|
|
37
|
+
|
|
38
|
+
### File format
|
|
39
|
+
|
|
40
|
+
To customize what level different modules are logged at, you should create a file that looks like this:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
[debug]
|
|
44
|
+
thds.adls.download
|
|
45
|
+
thds.core.link
|
|
46
|
+
[warning]
|
|
47
|
+
thds.mops.pure.pickle_runner
|
|
48
|
+
thds.mops.k8s.watch
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You may also/instead add an `*` to change the global default log level, e.g.:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
[warning]
|
|
55
|
+
*
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> The wildcard syntax is not a generic pattern-matching facility; it _only_ matches the root logger.
|
|
59
|
+
>
|
|
60
|
+
> However, if you wish to match a subtree of the logger hierarchy, this is built in with Python loggers;
|
|
61
|
+
> simply configure `thds.adls` under `[debug]` and all otherwise-unconfigured loggers under `thds.adls`
|
|
62
|
+
> will now log at the DEBUG level.
|
|
63
|
+
|
|
64
|
+
### `THDS_CORE_LOG_LEVELS_FILE` environment variable
|
|
65
|
+
|
|
66
|
+
Provide the path to the above-formatted file to `thds.core` via the `THDS_CORE_LOG_LEVELS_FILE`
|
|
67
|
+
environment variable. You may wish to create this file and then set its path via exported envvar in your
|
|
68
|
+
`.bash/zshrc` so that you can permanently tune our logging to meet your preferences.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
thds/core/__init__.py,sha256=imKpmnrBV0_7-1d1Pc2yR5jxbvOrmIplLm7Ig_eK1OU,934
|
|
2
|
+
thds/core/ansi_esc.py,sha256=QZ3CptZbX4N_hyP2IgqfTbNt9tBPaqy7ReTMQIzGbrc,870
|
|
3
|
+
thds/core/cache.py,sha256=nL0oAyZrhPqyBBLevnOWSWVoEBrftaG3aE6Qq6tvmAA,7153
|
|
4
|
+
thds/core/calgitver.py,sha256=HklIz-SczK92Vm2rXtTSDiVxAcxUW_GPVCRRGt4BmBA,2324
|
|
5
|
+
thds/core/concurrency.py,sha256=NQunF_tJ_z8cfVyhzkTPlb-nZrgu-vIk9_3XffgscKQ,3520
|
|
6
|
+
thds/core/config.py,sha256=N-WVpPDrfTSFKz0m7WrqZPBdd17dycpDx9nhbATkf3c,9092
|
|
7
|
+
thds/core/decos.py,sha256=VpFTKTArXepICxN4U8C8J6Z5KDq-yVjFZQzqs2jeVAk,1341
|
|
8
|
+
thds/core/dict_utils.py,sha256=MAVkGJg4KQN1UGBLEKuPdQucZaXg_jJakujQ-GUrYzw,6471
|
|
9
|
+
thds/core/env.py,sha256=M36CYkPZ5AUf_-n8EqjsMGwWOzaKEn0KgRwnqUK7jS4,1094
|
|
10
|
+
thds/core/exit_after.py,sha256=0lz63nz2NTiIdyBDYyRa9bQShxQKe7eISy8VhXeW4HU,3485
|
|
11
|
+
thds/core/files.py,sha256=8amNymTRuW17o6Qay73juxwkcW1DysWXeAByeofadkM,4540
|
|
12
|
+
thds/core/fretry.py,sha256=Tui2q6vXV6c7mjTa1czLrXiugHUEwQp-sZdiwXfxvmM,3829
|
|
13
|
+
thds/core/generators.py,sha256=rcdFpPj0NMJWSaSZTnBfTeZxTTORNB633Lng-BW1284,1939
|
|
14
|
+
thds/core/git.py,sha256=I6kaEvwcvVxCLYHhTTfnHle-GkmgOR9_fHs03QxgBfI,2792
|
|
15
|
+
thds/core/hash_cache.py,sha256=bkdV8HZOl66zNffnIjRvMQH1A0_xNqowMDLBtRXGFl8,3723
|
|
16
|
+
thds/core/hashing.py,sha256=5I4vLBjgkn9yOXMjBduLGdXsywBob-35qJTk_lks6aw,3505
|
|
17
|
+
thds/core/home.py,sha256=tTClL_AarIKeri1aNCpuIC6evD7qr83ESGD173B81hU,470
|
|
18
|
+
thds/core/hostname.py,sha256=canFGr-JaaG7nUfsQlyL0JT-2tnZoT1BvXzyaOMK1vA,208
|
|
19
|
+
thds/core/imports.py,sha256=0LVegY8I8_XKZPcqiIp2OVVzEDtyqYA3JETf9OAKNKs,568
|
|
20
|
+
thds/core/inspect.py,sha256=vCxKqw8XG2W1cuj0MwjdXhe9TLQrGdjRraS6UEYsbf8,1955
|
|
21
|
+
thds/core/iterators.py,sha256=d3iTQDR0gCW1nMRmknQeodR_4THzR9Ajmp8F8KCCFgg,208
|
|
22
|
+
thds/core/lazy.py,sha256=e1WvG4LsbEydV0igEr_Vl1cq05zlQNIE8MFYT90yglE,3289
|
|
23
|
+
thds/core/link.py,sha256=kmFJIFvEZc16-7S7IGvtTpzwl3VuvFl3yPlE6WJJ03w,5404
|
|
24
|
+
thds/core/merge_args.py,sha256=7oj7dtO1-XVkfTM3aBlq3QlZbo8tb6X7E3EVIR-60t8,5781
|
|
25
|
+
thds/core/meta.json,sha256=S_OtvqShWHyPjzX5ODjtw4SFDBQmP20V4QXw5fr2_a0,219
|
|
26
|
+
thds/core/meta.py,sha256=IPLAKrH06HooPMNf5FeqJvUcM-JljTGXddrAQ5oAX8E,16896
|
|
27
|
+
thds/core/parallel.py,sha256=lUzM0CRXsfzZgMMOHcwP-Th7bbXAqfl7FEGwdwAoBiM,7098
|
|
28
|
+
thds/core/pickle_visit.py,sha256=QNMWIi5buvk2zsvx1-D-FKL7tkrFUFDs387vxgGebgU,833
|
|
29
|
+
thds/core/prof.py,sha256=atDz7rDxzMzbKwgM48tics2tB5sBijKb_L5heAT6Pk8,8246
|
|
30
|
+
thds/core/progress.py,sha256=4YGbxliDl1i-k-88w4s86uy1E69eQ6xJySGPSkpH1QM,3358
|
|
31
|
+
thds/core/protocols.py,sha256=4na2EeWUDWfLn5-SxfMmKegDSndJ5z-vwMhDavhCpEM,409
|
|
32
|
+
thds/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
thds/core/scaling.py,sha256=f7CtdgK0sN6nroTq5hLAkG8xwbWhbCZUULstSKjoxO0,1615
|
|
34
|
+
thds/core/scope.py,sha256=iPRhS-lIe-axDctqxBtEPeF0PM_w-0tRS-9kPweUGBY,7205
|
|
35
|
+
thds/core/source.py,sha256=ACMe_e-0tuwUknOobomC9_q6vHM3hMTouWsSsJ-qHQk,9884
|
|
36
|
+
thds/core/source_serde.py,sha256=TqW4MTrXQ49JJaXrkmFcymSBIWvBlkxO2JOPNQap_F4,3523
|
|
37
|
+
thds/core/stack_context.py,sha256=17lPOuYWclUpZ-VXRkPgI4WbiMzq7_ZY6Kj1QK_1oNo,1332
|
|
38
|
+
thds/core/thunks.py,sha256=p1OvMBJ4VGMsD8BVA7zwPeAp0L3y_nxVozBF2E78t3M,1053
|
|
39
|
+
thds/core/timer.py,sha256=1FfcQ4-Gp6WQFXR0GKeT_8jwtamEfnTukdSbDKTAJVM,5432
|
|
40
|
+
thds/core/tmp.py,sha256=KgBAwQCmpm7I762eLRu-3MSfH3dKnqlrJkZ5nmPcRbc,3110
|
|
41
|
+
thds/core/types.py,sha256=Pbo5bF3b14V8eeabpnffWOPJDAGrMcKBCh5e-ZvipJk,70
|
|
42
|
+
thds/core/log/__init__.py,sha256=bDbZvlxyymY6VrQzD8lCn0egniLEiA9hpNMAXZ7e7wY,1348
|
|
43
|
+
thds/core/log/basic_config.py,sha256=WQDpLKFgu-BTdR22LMnGDK3jk4CUbP94JrjSsHgAjjg,6216
|
|
44
|
+
thds/core/log/json_formatter.py,sha256=C5bRsSbAqaQqfTm88jc3mYe3vwKZZLAxET8s7_u7aN0,1757
|
|
45
|
+
thds/core/log/kw_formatter.py,sha256=9-MVOd2r5NEkYNne9qWyFMeR5lac3w7mjHXsDa681i0,3379
|
|
46
|
+
thds/core/log/kw_logger.py,sha256=k9nckB0RRYU-X8Ols-pnxU_UuL6kqvtDs6MOi5JE7XY,3841
|
|
47
|
+
thds/core/log/logfmt.py,sha256=qS6BbdlOZPRnxmHenVL3uK43OQ30NJUnz92S6d_Yh2A,10360
|
|
48
|
+
thds/core/sqlite/__init__.py,sha256=tDMzuO76qTtckJHldPQ6nPZ6kcvhhoJrVuuW42JtaSQ,606
|
|
49
|
+
thds/core/sqlite/connect.py,sha256=l4QaSAI8RjP7Qh2FjmJ3EwRgfGf65Z3-LjtC9ocHM_U,977
|
|
50
|
+
thds/core/sqlite/copy.py,sha256=y3IRQTBrWDfKuVIfW7fYuEgwRCRKHjN0rxVFkIb9VrQ,1155
|
|
51
|
+
thds/core/sqlite/ddl.py,sha256=k9BvmDzb0rrlhmEpXkB6ESaZAUWtbL58x-70sPyoFk4,201
|
|
52
|
+
thds/core/sqlite/functions.py,sha256=AOIRzb7lNxmFm1J5JS6R8Nl-dSv3Dy47UNZVVjl1rvk,2158
|
|
53
|
+
thds/core/sqlite/index.py,sha256=Vc7qxPqQ69A6GO5gmVQf5e3y8f8IqOTHgyEDoVZxTFM,903
|
|
54
|
+
thds/core/sqlite/insert_utils.py,sha256=LUVcznl-xCVoh_L_6tabVYUAYnEnaVDmBX2PeopLMKU,884
|
|
55
|
+
thds/core/sqlite/merge.py,sha256=NxettDMJ_mcrWfteQn_ERY7MUB5ETR-yJLKg7uvF6zA,3779
|
|
56
|
+
thds/core/sqlite/meta.py,sha256=4P65PAmCjagHYO1Z6nWM-wkjEWv3hxw5qVa4cIpcH_8,5859
|
|
57
|
+
thds/core/sqlite/read.py,sha256=5pWvrbed3XNWgSy-79-8ONWkkt4jWbTzFNW6SnOrdYQ,2576
|
|
58
|
+
thds/core/sqlite/sqlmap.py,sha256=YzLsTXwvEWcPI_KmFjA4tKfPoSP3gpHThyIthmPVyK0,6778
|
|
59
|
+
thds/core/sqlite/structured.py,sha256=swCbDoyVT6cE7Kl79Wh_rg5Z1-yrUDJbiVJF4bjsetU,4641
|
|
60
|
+
thds/core/sqlite/types.py,sha256=oUkfoKRYNGDPZRk29s09rc9ha3SCk2SKr_K6WKebBFs,1308
|
|
61
|
+
thds/core/sqlite/upsert.py,sha256=BmKK6fsGVedt43iY-Lp7dnAu8aJ1e9CYlPVEQR2pMj4,5827
|
|
62
|
+
thds/core/sqlite/write.py,sha256=h-tld6sC1alkaF92KRdrjyrEOwCt3bZ804muOcV4MEw,3141
|
|
63
|
+
thds.core-1.31.20250116223856.dist-info/METADATA,sha256=vSPFjc1mry08Oj6Cy6Ouwk0G4r7sLRPml9RCVBoGWgU,2123
|
|
64
|
+
thds.core-1.31.20250116223856.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
65
|
+
thds.core-1.31.20250116223856.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
|
|
66
|
+
thds.core-1.31.20250116223856.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
|
|
67
|
+
thds.core-1.31.20250116223856.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
thds
|
thds.core-0.0.1.dist-info/RECORD
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
thds.core-0.0.1.dist-info/METADATA,sha256=58tW_pFtdIcYkNB1KrZ6kYpTpg_kLOesVF_9G8vPtLY,228
|
|
2
|
-
thds.core-0.0.1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
3
|
-
thds.core-0.0.1.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
4
|
-
thds.core-0.0.1.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|