thds.core 0.0.1__py3-none-any.whl → 1.31.20250123022540__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.

Files changed (70) hide show
  1. thds/core/__init__.py +48 -0
  2. thds/core/ansi_esc.py +46 -0
  3. thds/core/cache.py +201 -0
  4. thds/core/calgitver.py +82 -0
  5. thds/core/concurrency.py +100 -0
  6. thds/core/config.py +250 -0
  7. thds/core/decos.py +55 -0
  8. thds/core/dict_utils.py +188 -0
  9. thds/core/env.py +40 -0
  10. thds/core/exit_after.py +121 -0
  11. thds/core/files.py +125 -0
  12. thds/core/fretry.py +115 -0
  13. thds/core/generators.py +56 -0
  14. thds/core/git.py +81 -0
  15. thds/core/hash_cache.py +86 -0
  16. thds/core/hashing.py +106 -0
  17. thds/core/home.py +15 -0
  18. thds/core/hostname.py +10 -0
  19. thds/core/imports.py +17 -0
  20. thds/core/inspect.py +58 -0
  21. thds/core/iterators.py +9 -0
  22. thds/core/lazy.py +83 -0
  23. thds/core/link.py +153 -0
  24. thds/core/log/__init__.py +29 -0
  25. thds/core/log/basic_config.py +171 -0
  26. thds/core/log/json_formatter.py +43 -0
  27. thds/core/log/kw_formatter.py +84 -0
  28. thds/core/log/kw_logger.py +93 -0
  29. thds/core/log/logfmt.py +302 -0
  30. thds/core/merge_args.py +168 -0
  31. thds/core/meta.json +8 -0
  32. thds/core/meta.py +518 -0
  33. thds/core/parallel.py +200 -0
  34. thds/core/pickle_visit.py +24 -0
  35. thds/core/prof.py +276 -0
  36. thds/core/progress.py +112 -0
  37. thds/core/protocols.py +17 -0
  38. thds/core/py.typed +0 -0
  39. thds/core/scaling.py +39 -0
  40. thds/core/scope.py +199 -0
  41. thds/core/source.py +238 -0
  42. thds/core/source_serde.py +104 -0
  43. thds/core/sqlite/__init__.py +21 -0
  44. thds/core/sqlite/connect.py +33 -0
  45. thds/core/sqlite/copy.py +35 -0
  46. thds/core/sqlite/ddl.py +4 -0
  47. thds/core/sqlite/functions.py +63 -0
  48. thds/core/sqlite/index.py +22 -0
  49. thds/core/sqlite/insert_utils.py +23 -0
  50. thds/core/sqlite/merge.py +84 -0
  51. thds/core/sqlite/meta.py +190 -0
  52. thds/core/sqlite/read.py +66 -0
  53. thds/core/sqlite/sqlmap.py +179 -0
  54. thds/core/sqlite/structured.py +138 -0
  55. thds/core/sqlite/types.py +64 -0
  56. thds/core/sqlite/upsert.py +139 -0
  57. thds/core/sqlite/write.py +99 -0
  58. thds/core/stack_context.py +41 -0
  59. thds/core/thunks.py +40 -0
  60. thds/core/timer.py +214 -0
  61. thds/core/tmp.py +85 -0
  62. thds/core/types.py +4 -0
  63. thds.core-1.31.20250123022540.dist-info/METADATA +68 -0
  64. thds.core-1.31.20250123022540.dist-info/RECORD +67 -0
  65. {thds.core-0.0.1.dist-info → thds.core-1.31.20250123022540.dist-info}/WHEEL +1 -1
  66. thds.core-1.31.20250123022540.dist-info/entry_points.txt +4 -0
  67. thds.core-1.31.20250123022540.dist-info/top_level.txt +1 -0
  68. thds.core-0.0.1.dist-info/METADATA +0 -8
  69. thds.core-0.0.1.dist-info/RECORD +0 -4
  70. 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,4 @@
1
+ import os
2
+ import typing as ty
3
+
4
+ StrOrPath = ty.Union[str, os.PathLike]
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.2
2
+ Name: thds.core
3
+ Version: 1.31.20250123022540
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-apSgOHIJCMM_eCts6QUJds_IvZNo4eQxU4ycV6WV0,196
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.20250123022540.dist-info/METADATA,sha256=0cuvrBDAhkXS8qA87Es8TNm7jFoVj1VVcQUlc_BcJSc,2123
64
+ thds.core-1.31.20250123022540.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
65
+ thds.core-1.31.20250123022540.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
66
+ thds.core-1.31.20250123022540.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
67
+ thds.core-1.31.20250123022540.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ new-calgitver = thds.core.meta:print_calgitver
3
+ show-thds-config = thds.core.config:show_config_cli
4
+ thds-exit-after = thds.core.exit_after:main
@@ -1,8 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: thds.core
3
- Version: 0.0.1
4
- Home-page: https://www.trillianthealth.com
5
- Author: Trilliant Health Data Science
6
- Author-email: datascience@trillianthealth.com
7
- Classifier: Development Status :: 1 - Planning
8
-
@@ -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
-