QuLab 2.11.13__py3-none-any.whl → 2.11.15__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.
qulab/cli/config.py CHANGED
@@ -3,6 +3,7 @@ import functools
3
3
  import os
4
4
  import sys
5
5
  from pathlib import Path
6
+ from typing import Any
6
7
 
7
8
  import click
8
9
  from loguru import logger
@@ -11,6 +12,34 @@ DEFAULT_CONFIG_PATH = Path(os.path.expanduser("~/.qulab.ini"))
11
12
  ENV_PREFIX = "QULAB_"
12
13
 
13
14
 
15
+ def find_config_file() -> Path | None:
16
+ """
17
+ 查找配置文件
18
+ """
19
+ # 环境变量
20
+ if config_path := os.getenv("QULAB_CONFIG_PATH"):
21
+ return Path(config_path)
22
+
23
+ # 先查找当前目录下的配置文件
24
+ config_path = Path.cwd() / "qulab.ini"
25
+ if config_path.exists():
26
+ return config_path
27
+
28
+ # 向上级目录查找,直到找到 uv 项目根目录
29
+ current_dir = Path.cwd()
30
+ while not (current_dir / "uv.lock").exists():
31
+ current_dir = current_dir.parent
32
+ if current_dir == Path("/"):
33
+ break
34
+ config_path = current_dir / "qulab.ini"
35
+ if config_path.exists():
36
+ return config_path
37
+
38
+ if DEFAULT_CONFIG_PATH.exists():
39
+ return DEFAULT_CONFIG_PATH
40
+ return None
41
+
42
+
14
43
  def _get_config_value(option_name,
15
44
  type_cast=str,
16
45
  command_name=None,
@@ -37,11 +66,9 @@ def _get_config_value(option_name,
37
66
  # 先加载默认配置防止段不存在
38
67
  config.read_dict({config_section: {}})
39
68
 
40
- config_path = Path.cwd() / "qulab.ini"
41
- if config_path.exists():
69
+ config_path = find_config_file()
70
+ if config_path is not None:
42
71
  config.read(config_path)
43
- elif DEFAULT_CONFIG_PATH.exists():
44
- config.read(DEFAULT_CONFIG_PATH)
45
72
 
46
73
  # 从对应配置段读取
47
74
  if config.has_section(config_section):
@@ -57,10 +84,10 @@ def _get_config_value(option_name,
57
84
  return default # 交给 Click 处理默认值
58
85
 
59
86
 
60
- def get_config_value(option_name,
61
- type_cast=str,
62
- command_name=None,
63
- default=None):
87
+ def get_config_value(option_name: str,
88
+ type_cast: type = str,
89
+ command_name: str | None = None,
90
+ default: Any = None):
64
91
  """
65
92
  获取配置值,支持命令专属配置的优先级获取
66
93
 
@@ -1,3 +1,4 @@
1
+ import copy
1
2
  import importlib
2
3
  import os
3
4
  import sys
@@ -157,6 +158,128 @@ def _init():
157
158
  _init()
158
159
 
159
160
 
161
+ class RegistrySnapshot:
162
+
163
+ def __init__(self, data: dict[str, Any]):
164
+ self.data = copy.deepcopy(data)
165
+
166
+ def query(self, key: str, default=...) -> Any:
167
+ """
168
+ Query a value from the nested dictionary using dot-separated keys.
169
+
170
+ Args:
171
+ key: Dot-separated key path (e.g., 'level1.level2.level3')
172
+ default: Default value to return if key not found.
173
+ If not provided and key is missing, raises KeyError.
174
+
175
+ Returns:
176
+ The value at the specified path, default value, or raises KeyError
177
+ """
178
+ keys = key.split('.')
179
+ current = self.data
180
+
181
+ # Track which level we're at for error reporting
182
+ for i, k in enumerate(keys):
183
+ if isinstance(current, dict) and k in current:
184
+ current = current[k]
185
+ else:
186
+ # Key not found at this level
187
+ missing_path = '.'.join(keys[:i + 1])
188
+ if default is ...:
189
+ raise KeyError(
190
+ f"Key '{missing_path}' not found in registry (failed at level {i+1})"
191
+ )
192
+ return default
193
+
194
+ return current
195
+
196
+ def get(self, key: str, default=...) -> Any:
197
+ """
198
+ Query a value from the nested dictionary using dot-separated keys.
199
+
200
+ Args:
201
+ key: Dot-separated key path (e.g., 'level1.level2.level3')
202
+ default: Default value to return if key not found.
203
+ If not provided and key is missing, raises KeyError.
204
+
205
+ Returns:
206
+ The value at the specified path, default value, or raises KeyError
207
+ """
208
+ return self.query(key, default)
209
+
210
+ def export(self) -> dict[str, Any]:
211
+ return self.data
212
+
213
+ def set(self, key: str, value: Any):
214
+ """
215
+ Set a value in the nested dictionary using dot-separated keys.
216
+ Creates intermediate dictionaries if they don't exist.
217
+
218
+ Args:
219
+ key: Dot-separated key path (e.g., 'level1.level2.level3')
220
+ value: Value to set
221
+ """
222
+ keys = key.split('.')
223
+ current = self.data
224
+
225
+ # Navigate to the parent of the target key, creating dicts as needed
226
+ for k in keys[:-1]:
227
+ if k not in current:
228
+ current[k] = {}
229
+ elif not isinstance(current[k], dict):
230
+ # If existing value is not a dict, replace it with a dict
231
+ current[k] = {}
232
+ current = current[k]
233
+
234
+ # Set the final value
235
+ current[keys[-1]] = value
236
+
237
+ def delete(self, key: str):
238
+ """
239
+ Delete a value from the nested dictionary using dot-separated keys.
240
+ Also cleans up empty parent dictionaries after deletion.
241
+
242
+ Args:
243
+ key: Dot-separated key path (e.g., 'level1.level2.level3')
244
+ """
245
+ keys = key.split('.')
246
+
247
+ # First check if the path exists
248
+ try:
249
+ self.query(key)
250
+ except KeyError:
251
+ return # Path doesn't exist, nothing to delete
252
+
253
+ # Navigate and collect references to all parent dictionaries
254
+ path_refs = []
255
+ current = self.data
256
+
257
+ for k in keys[:-1]:
258
+ path_refs.append((current, k))
259
+ current = current[k]
260
+
261
+ # Delete the final key
262
+ if isinstance(current, dict) and keys[-1] in current:
263
+ del current[keys[-1]]
264
+
265
+ # Clean up empty dictionaries from leaf to root
266
+ for parent_dict, parent_key in reversed(path_refs):
267
+ # Check if the current dictionary is empty
268
+ if isinstance(current, dict) and len(current) == 0:
269
+ del parent_dict[parent_key]
270
+ current = parent_dict
271
+ else:
272
+ # Stop if we encounter a non-empty dictionary
273
+ break
274
+
275
+ def clear(self):
276
+ self.data.clear()
277
+
278
+ def update(self, parameters: dict[str, Any]):
279
+ for k, v in parameters.items():
280
+ self.set(k, v)
281
+
282
+
160
283
  class Registry():
161
284
 
162
285
  def __init__(self):
@@ -183,3 +306,6 @@ class Registry():
183
306
 
184
307
  def update(self, parameters: dict[str, Any]):
185
308
  return self.api[1](parameters)
309
+
310
+ def snapshot(self) -> RegistrySnapshot:
311
+ return RegistrySnapshot(self.export())
@@ -236,6 +236,8 @@ def inject_mapping(source: str, mapping: dict[str, Any],
236
236
  if default_value is not None:
237
237
  # 清除 default 值前后的空格
238
238
  clean_value = default_value.strip()
239
+ if var_name not in mapping:
240
+ return clean_value
239
241
  return f'{base}.get({quote}{var_name}{quote}, {clean_value})'
240
242
  else:
241
243
  return f'{base}[{quote}{var_name}{quote}]'
qulab/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.11.13"
1
+ __version__ = "2.11.15"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuLab
3
- Version: 2.11.13
3
+ Version: 2.11.15
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -2,19 +2,19 @@ qulab/__init__.py,sha256=hmf6R3jiM5NtJY1mSptogYnH5DMTR2dTzlXykqLxQzg,2027
2
2
  qulab/__main__.py,sha256=fjaRSL_uUjNIzBGNgjlGswb9TJ2VD5qnkZHW3hItrD4,68
3
3
  qulab/typing.py,sha256=vg62sGqxuD9CI5677ejlzAmf2fVdAESZCQjAE_xSxPg,69
4
4
  qulab/utils.py,sha256=B_8QdY9OMY7WU-F200v93BDJXJpQcKAHihnOXeEvv_w,3966
5
- qulab/version.py,sha256=zzz5acK8diCvnDna0wJqi0GGAyMkaKL0pR-gfoeXbEc,23
5
+ qulab/version.py,sha256=Xwyp7A0xHh2GDh3MUxhzaH2tfp0EQ4tK8L1vQV9D1ow,23
6
6
  qulab/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  qulab/cli/commands.py,sha256=ywKmwbGNBCVASx8OsgrRttaLz9ogwY38ZdKj7I-tdJ4,813
8
- qulab/cli/config.py,sha256=RIQlCTViDOg_QA0J7Y0jNJ8TEXG7ATmGwbK2Ief0NPY,5591
8
+ qulab/cli/config.py,sha256=Z-lePcDMeeQmwFpdfQHqZuaXTPhWUHN5KrlSqrya73Y,6324
9
9
  qulab/cli/decorators.py,sha256=oImteZVnDPPWdyhJ4kzf2KYGJLON7VsKGBvZadWLQZo,621
10
10
  qulab/executor/__init__.py,sha256=LosPzOMaljSZY1thy_Fxtbrgq7uubJszMABEB7oM7tU,101
11
11
  qulab/executor/analyze.py,sha256=VYTfiOYJe5gDbtGA4wOkrQhhu55iIrRVByx-gBLUUm4,5672
12
12
  qulab/executor/cli.py,sha256=fTB7V8xNCvpU8EMa__ysBvRCzruP-QFbXOyLpeTPx0E,25711
13
13
  qulab/executor/load.py,sha256=eMlzOrrn8GpbP3J3uY5JJ8iO7tL5B1DWP1z2qiGyNhU,20385
14
- qulab/executor/registry.py,sha256=gym9F5FIDY5eV-cSCZsP99wC4l-6jkx9VMjJMaPOLaQ,4730
14
+ qulab/executor/registry.py,sha256=CPY5PNTbE4Rw13Ucsq1wBk3fgvoSB3_J-iyRFdNNl0g,9044
15
15
  qulab/executor/schedule.py,sha256=cDFM_tIMvxHLxb-tBA6fEPF9u-4LcgnQDxbaquZIQKc,21727
16
16
  qulab/executor/storage.py,sha256=8K73KGLAVgchJdtd4rKHXkr1CQOJORWH-Gi57w8IYsw,21081
17
- qulab/executor/template.py,sha256=D8D7_B__R8wh1oOXk7CKZveRqDix4dNeUjefbfN6m9w,10720
17
+ qulab/executor/template.py,sha256=Fxou5lglaCnEhJTKDotFaRrWT4csAZIgmHz9EFbPnYs,10811
18
18
  qulab/executor/utils.py,sha256=YSUEYjyYTJTjiJmd-l5GTEnSyD6bqEdNoWhivEAfGZA,7112
19
19
  qulab/monitor/__init__.py,sha256=FNYMIUr8ycRM4XmsAGpRK-A-SQsJZlcRWghXcGTgAi0,44
20
20
  qulab/monitor/__main__.py,sha256=Txy4d7vgVYUWwiHxX3uwtsi1ewde-MaiSC_K0ePoPiM,1372
@@ -96,9 +96,9 @@ qulab/visualization/plot_seq.py,sha256=UWTS6p9nfX_7B8ehcYo6UnSTUCjkBsNU9jiOeW2ca
96
96
  qulab/visualization/qdat.py,sha256=ZeevBYWkzbww4xZnsjHhw7wRorJCBzbG0iEu-XQB4EA,5735
97
97
  qulab/visualization/rot3d.py,sha256=CuLfG1jw1032HNNZxzC0OWtNzKsbj2e2WRYhMWDgvMQ,1321
98
98
  qulab/visualization/widgets.py,sha256=6KkiTyQ8J-ei70LbPQZAK35wjktY47w2IveOa682ftA,3180
99
- qulab-2.11.13.dist-info/licenses/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
100
- qulab-2.11.13.dist-info/METADATA,sha256=Bo0lvlENxUP4QCQsnn0VzTpNuJAagUPHwP3DZXWiif4,3945
101
- qulab-2.11.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
102
- qulab-2.11.13.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
103
- qulab-2.11.13.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
104
- qulab-2.11.13.dist-info/RECORD,,
99
+ qulab-2.11.15.dist-info/licenses/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
100
+ qulab-2.11.15.dist-info/METADATA,sha256=wwaZbMBIXvh3I9Gc2xzjmwqK10kmhsFaTHVTs11Ypl0,3945
101
+ qulab-2.11.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
102
+ qulab-2.11.15.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
103
+ qulab-2.11.15.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
104
+ qulab-2.11.15.dist-info/RECORD,,