thds.core 1.32.20250219165944__py3-none-any.whl → 1.32.20250219172103__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/config.py CHANGED
@@ -94,6 +94,14 @@ def _type_parser(default: T) -> ty.Callable[[ty.Any], T]:
94
94
  return lambda x: x # we can't infer a type parser, so we'll return the default no-op parser.
95
95
 
96
96
 
97
+ class ConfigRegistry(ty.Dict[str, "ConfigItem"]): # noqa: B903
98
+ def __init__(self, name: str):
99
+ self.name = name
100
+
101
+
102
+ _DEFAULT_REGISTRY = ConfigRegistry("default")
103
+
104
+
97
105
  class ConfigItem(ty.Generic[T]):
98
106
  """Should only ever be constructed at a module level."""
99
107
 
@@ -106,12 +114,17 @@ class ConfigItem(ty.Generic[T]):
106
114
  *,
107
115
  parse: ty.Optional[ty.Callable[[ty.Any], T]] = None,
108
116
  secret: bool = False,
117
+ registry: ConfigRegistry = _DEFAULT_REGISTRY,
118
+ name_transform: ty.Callable[[str], str] = _fullname,
109
119
  ):
120
+ """parse should be an idempotent parser. In other words, parse(parse(x)) == parse(x)"""
110
121
  self.secret = secret
111
- name = _fullname(name)
112
- if name in _REGISTRY:
113
- raise ConfigNameCollisionError(f"Config item {name} has already been registered!")
114
- _REGISTRY[name] = self
122
+ name = name_transform(name)
123
+ if name in registry:
124
+ raise ConfigNameCollisionError(
125
+ f"Config item {name} has already been registered in {registry.name}!"
126
+ )
127
+ registry[name] = self
115
128
  self.name = name
116
129
  self.parse = parse or _type_parser(default)
117
130
  raw_resolved_global = _getenv(name, secret=secret)
@@ -156,6 +169,9 @@ class ConfigItem(ty.Generic[T]):
156
169
  raise UnconfiguredError(f"Config item '{self.name}' has not been configured!")
157
170
  return self.global_value
158
171
 
172
+ def __repr__(self) -> str:
173
+ return f"ConfigItem('{self.name}', {self()})"
174
+
159
175
 
160
176
  def tobool(s_or_b: ty.Union[str, bool]) -> bool:
161
177
  """A reasonable implementation that we could expand in the future."""
@@ -171,55 +187,70 @@ item = ConfigItem
171
187
  # a short alias
172
188
 
173
189
 
174
- _REGISTRY: ty.Dict[str, ConfigItem] = dict()
190
+ def config_by_name(
191
+ name: str,
192
+ registry: ConfigRegistry = _DEFAULT_REGISTRY,
193
+ ) -> ConfigItem:
194
+ """This is a dynamic interface - in general, prefer accessing the ConfigItem object directly."""
195
+ return registry[_fullname(name)]
175
196
 
176
197
 
177
- def config_by_name(name: str) -> ConfigItem:
178
- """This is a dynamic interface - in general, prefer accessing the ConfigItem object directly."""
179
- return _REGISTRY[_fullname(name)]
198
+ def flatten_config(config: ty.Mapping[str, ty.Any]) -> ty.Dict[str, ty.Any]:
199
+ """This is a helper function to flatten a nested configuration dictionary."""
200
+ flat = dict()
201
+ for key, value in config.items():
202
+ if isinstance(value, dict):
203
+ for subkey, subvalue in flatten_config(value).items():
204
+ flat[f"{key}.{subkey}"] = subvalue
205
+ else:
206
+ flat[key] = value
207
+ return flat
180
208
 
181
209
 
182
- def set_global_defaults(config: ty.Dict[str, ty.Any]):
210
+ def set_global_defaults(
211
+ config: ty.Mapping[str, ty.Any],
212
+ registry: ConfigRegistry = _DEFAULT_REGISTRY,
213
+ ):
183
214
  """Any config-file parser can create a dictionary of only the
184
215
  items it managed to read, and then all of those can be set at once
185
216
  via this function.
186
217
  """
187
- for name, value in config.items():
188
- if not isinstance(value, dict):
218
+ flat_config = flatten_config(config)
219
+ for name, value in flat_config.items():
220
+ try:
221
+ config_item = registry[name]
222
+ config_item.set_global(config_item.parse(value))
223
+ except KeyError:
224
+ # try directly importing a module - this is only best-effort and will not work
225
+ # if you did not follow standard configuration naming conventions.
226
+ import importlib
227
+
228
+ maybe_module_name = ".".join(name.split(".")[:-1])
229
+
189
230
  try:
190
- config_item = _REGISTRY[name]
191
- config_item.set_global(config_item.parse(value))
192
- except KeyError:
193
- # try directly importing a module - this is only best-effort and will not work
194
- # if you did not follow standard configuration naming conventions.
195
- import importlib
196
-
197
- maybe_module_name = ".".join(name.split(".")[:-1])
231
+ importlib.import_module(maybe_module_name)
198
232
  try:
199
- importlib.import_module(maybe_module_name)
200
- try:
201
- config_item = _REGISTRY[name]
202
- config_item.set_global(config_item.parse(value))
203
- except KeyError as kerr:
204
- raise KeyError(
205
- f"Config item {name} is not registered"
206
- f" and no module with the name {maybe_module_name} was importable."
207
- " Please double-check your configuration."
208
- ) from kerr
209
- except ModuleNotFoundError:
210
- # create a new, dynamic config item that will only be accessible via
211
- # its name.
212
- ConfigItem(name, value) # return value not needed since it self-registers.
213
- getLogger(__name__).debug(
214
- "Created dynamic config item '%s' with value '%s'", name, value
215
- )
216
-
217
- else: # recurse
218
- set_global_defaults({f"{name}.{key}": val for key, val in value.items()})
219
-
220
-
221
- def get_all_config() -> ty.Dict[str, ty.Any]:
222
- return {k: v() if not v.secret else "***SECRET***" for k, v in _REGISTRY.items()}
233
+ config_item = registry[name]
234
+ config_item.set_global(config_item.parse(value))
235
+ except KeyError as kerr:
236
+ raise KeyError(
237
+ f"Config item {name} is not registered"
238
+ f" and no module with the name {maybe_module_name} was importable."
239
+ " Please double-check your configuration."
240
+ ) from kerr
241
+ except ModuleNotFoundError:
242
+ # create a new, dynamic config item that will only be accessible via
243
+ # its name.
244
+ ConfigItem(
245
+ name, value, registry=registry
246
+ ) # return value not needed since it self-registers.
247
+ getLogger(__name__).debug(
248
+ "Created dynamic config item '%s' with value '%s'", name, value
249
+ )
250
+
251
+
252
+ def get_all_config(registry: ty.Dict[str, ConfigItem] = _DEFAULT_REGISTRY) -> ty.Dict[str, ty.Any]:
253
+ return {k: v() if not v.secret else "***SECRET***" for k, v in registry.items()}
223
254
 
224
255
 
225
256
  def show_config_cli():
thds/core/files.py CHANGED
@@ -36,7 +36,7 @@ def path_from_uri(uri: str) -> Path:
36
36
  str_path = remove_file_scheme(uri)
37
37
  if not str_path:
38
38
  raise ValueError('Cannot convert an empty string to a Path. Did you mean to use "."?')
39
- return Path(str_path)
39
+ return Path(str_path).expanduser().resolve()
40
40
 
41
41
 
42
42
  def to_uri(path: Path) -> str:
thds/core/meta.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "git_commit": "affac0a04726de27f8065525d193df4cd6376b9c",
3
- "git_branch": "main",
3
+ "git_branch": "task/dbxtend/use-pure-magic",
4
4
  "git_is_clean": true,
5
- "pyproject_version": "1.32.20250219165944",
6
- "thds_user": "runner",
5
+ "pyproject_version": "1.32.20250219172103",
6
+ "thds_user": "peter.gaultney",
7
7
  "misc": {}
8
8
  }
thds/core/source/src.py CHANGED
@@ -3,7 +3,7 @@ import typing as ty
3
3
  from dataclasses import dataclass
4
4
  from pathlib import Path
5
5
 
6
- from .. import hashing
6
+ from .. import hashing, types
7
7
  from . import _download
8
8
 
9
9
 
@@ -84,3 +84,17 @@ class Source(os.PathLike):
84
84
 
85
85
  def __fspath__(self) -> str:
86
86
  return os.fspath(self.path())
87
+
88
+ @staticmethod
89
+ def from_file(
90
+ filename: types.StrOrPath, hash: ty.Optional[hashing.Hash] = None, uri: str = ""
91
+ ) -> "Source":
92
+ from ._construct import from_file
93
+
94
+ return from_file(filename, hash, uri)
95
+
96
+ @staticmethod
97
+ def from_uri(uri: str, hash: ty.Optional[hashing.Hash] = None) -> "Source":
98
+ from ._construct import from_uri
99
+
100
+ return from_uri(uri, hash)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: thds.core
3
- Version: 1.32.20250219165944
3
+ Version: 1.32.20250219172103
4
4
  Summary: Core utilities.
5
5
  Author: Trilliant Health
6
6
  Description-Content-Type: text/markdown
@@ -4,12 +4,12 @@ thds/core/cache.py,sha256=nL0oAyZrhPqyBBLevnOWSWVoEBrftaG3aE6Qq6tvmAA,7153
4
4
  thds/core/calgitver.py,sha256=HklIz-SczK92Vm2rXtTSDiVxAcxUW_GPVCRRGt4BmBA,2324
5
5
  thds/core/cm.py,sha256=WZB8eQU0DaBYj9s97nc3PuCtai9guovfyiQH68zhLzY,1086
6
6
  thds/core/concurrency.py,sha256=NQunF_tJ_z8cfVyhzkTPlb-nZrgu-vIk9_3XffgscKQ,3520
7
- thds/core/config.py,sha256=N-WVpPDrfTSFKz0m7WrqZPBdd17dycpDx9nhbATkf3c,9092
7
+ thds/core/config.py,sha256=VWymw6pqPRvX7wwsJ0Y-D2gLoCclAHhARmTnuUw7kb0,10014
8
8
  thds/core/decos.py,sha256=VpFTKTArXepICxN4U8C8J6Z5KDq-yVjFZQzqs2jeVAk,1341
9
9
  thds/core/dict_utils.py,sha256=MAVkGJg4KQN1UGBLEKuPdQucZaXg_jJakujQ-GUrYzw,6471
10
10
  thds/core/env.py,sha256=M36CYkPZ5AUf_-n8EqjsMGwWOzaKEn0KgRwnqUK7jS4,1094
11
11
  thds/core/exit_after.py,sha256=0lz63nz2NTiIdyBDYyRa9bQShxQKe7eISy8VhXeW4HU,3485
12
- thds/core/files.py,sha256=35vhbaDv4OkL_n1PCM-ki54708aibFMrnURpth_5UsA,4556
12
+ thds/core/files.py,sha256=NJlPXj7BejKd_Pa06MOywVv_YapT4bVedfsJHrWX8nI,4579
13
13
  thds/core/fretry.py,sha256=Tui2q6vXV6c7mjTa1czLrXiugHUEwQp-sZdiwXfxvmM,3829
14
14
  thds/core/generators.py,sha256=rcdFpPj0NMJWSaSZTnBfTeZxTTORNB633Lng-BW1284,1939
15
15
  thds/core/git.py,sha256=I6kaEvwcvVxCLYHhTTfnHle-GkmgOR9_fHs03QxgBfI,2792
@@ -24,7 +24,7 @@ thds/core/lazy.py,sha256=e1WvG4LsbEydV0igEr_Vl1cq05zlQNIE8MFYT90yglE,3289
24
24
  thds/core/link.py,sha256=kmFJIFvEZc16-7S7IGvtTpzwl3VuvFl3yPlE6WJJ03w,5404
25
25
  thds/core/logical_root.py,sha256=gWkIYRv9kNQfzbpxJaYiwNXVz1neZ2NvnvProtOn9d8,1399
26
26
  thds/core/merge_args.py,sha256=7oj7dtO1-XVkfTM3aBlq3QlZbo8tb6X7E3EVIR-60t8,5781
27
- thds/core/meta.json,sha256=8W_-XoFSxLU_TCVyOLAH5tBlGCDKqPiVxds08-bxUTs,196
27
+ thds/core/meta.json,sha256=dKSxAvJfyiieovIECa_e5kibVmRg29mmOmqqHGS5Mfs,227
28
28
  thds/core/meta.py,sha256=IPLAKrH06HooPMNf5FeqJvUcM-JljTGXddrAQ5oAX8E,16896
29
29
  thds/core/parallel.py,sha256=HXAn9aIYqNE5rnRN5ypxR6CUucdfzE5T5rJ_MUv-pFk,7590
30
30
  thds/core/pickle_visit.py,sha256=QNMWIi5buvk2zsvx1-D-FKL7tkrFUFDs387vxgGebgU,833
@@ -50,7 +50,7 @@ thds/core/source/__init__.py,sha256=RiaUHNunoaw4XJUrwR5vJzSS6HGxOUKUONR_ipX5654,
50
50
  thds/core/source/_construct.py,sha256=klN6-fSJrsbbUhp92wzhJcF73h_PKKJItNLC__vwlIs,3122
51
51
  thds/core/source/_download.py,sha256=pUhkphHdB7y4ZpxZZ6ITIS5giXMHuRf420yYAJwx6aE,2924
52
52
  thds/core/source/serde.py,sha256=wXCfuv_Dv3QvJJr-uebGmTrfhCU_1a8VX3VJnXhVHfU,3539
53
- thds/core/source/src.py,sha256=A1PSR5vANLwnUWLsFNVLkkeUdaidzRAzq8vri_a5w9E,4141
53
+ thds/core/source/src.py,sha256=9A_8kSBUc5k6OLAYe5EW_VogpXFIqofow7Rxl8xv-eg,4559
54
54
  thds/core/source/tree.py,sha256=vjAqnQXGE0XiI0WvlLyXGqEAZbyjq6XmdUeWAR0HI4M,4144
55
55
  thds/core/sqlite/__init__.py,sha256=tDMzuO76qTtckJHldPQ6nPZ6kcvhhoJrVuuW42JtaSQ,606
56
56
  thds/core/sqlite/connect.py,sha256=l4QaSAI8RjP7Qh2FjmJ3EwRgfGf65Z3-LjtC9ocHM_U,977
@@ -67,8 +67,8 @@ thds/core/sqlite/structured.py,sha256=swCbDoyVT6cE7Kl79Wh_rg5Z1-yrUDJbiVJF4bjset
67
67
  thds/core/sqlite/types.py,sha256=oUkfoKRYNGDPZRk29s09rc9ha3SCk2SKr_K6WKebBFs,1308
68
68
  thds/core/sqlite/upsert.py,sha256=BmKK6fsGVedt43iY-Lp7dnAu8aJ1e9CYlPVEQR2pMj4,5827
69
69
  thds/core/sqlite/write.py,sha256=z0219vDkQDCnsV0WLvsj94keItr7H4j7Y_evbcoBrWU,3458
70
- thds.core-1.32.20250219165944.dist-info/METADATA,sha256=a_qO5xgSI8sxxMLy0Zw0nxFXI33RhRkeQwYw_XSmYoY,2123
71
- thds.core-1.32.20250219165944.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
72
- thds.core-1.32.20250219165944.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
73
- thds.core-1.32.20250219165944.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
74
- thds.core-1.32.20250219165944.dist-info/RECORD,,
70
+ thds.core-1.32.20250219172103.dist-info/METADATA,sha256=BVFThCu8DvjqINHhfNv2Ztt8jraXEiQKAj0h3wLzQdg,2123
71
+ thds.core-1.32.20250219172103.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
72
+ thds.core-1.32.20250219172103.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
73
+ thds.core-1.32.20250219172103.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
74
+ thds.core-1.32.20250219172103.dist-info/RECORD,,