xenoslib 0.1.30__py3-none-any.whl → 0.2.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.
xenoslib/about.py CHANGED
@@ -4,4 +4,4 @@ __url__ = "https://github.com/XenosLu/xenoslib.git"
4
4
  __author__ = "Xenocider"
5
5
  __author_email__ = "xenos.lu@gmail.com"
6
6
  __license__ = "MIT License"
7
- __version__ = "0.1.30"
7
+ __version__ = "0.2.0"
@@ -28,36 +28,37 @@ class ConfigLoader(SingletonWithArgs):
28
28
 
29
29
  # With Vault (hvac imported on demand)
30
30
  >>> config = ConfigLoader("config.yml", vault_secret_id="my-secret-id")
31
+
32
+ # Write to Vault using attribute style
33
+ >>> config.test_section.test_key = "new_value"
34
+
35
+ # Write to Vault using dictionary style
36
+ >>> config["test_section"]["test_key"] = "new_value"
31
37
  """
32
38
 
39
+ VAULT_SUFFIX = "@vault"
40
+ KV_MOUNT_POINT = "kv"
41
+
33
42
  cache = {}
34
43
  vault_client = None
35
44
 
36
45
  def __init__(self, config_file_path="config.yml", vault_secret_id=None):
37
46
  """Initialize the ConfigLoader with a configuration file and optional Vault secret."""
38
47
  with open(config_file_path, "r") as f:
39
- self._raw_config = yaml.safe_load(f) or {}
40
-
48
+ config_data = yaml.safe_load(f)
49
+ self._raw_config = config_data if isinstance(config_data, dict) else {}
50
+
41
51
  if vault_secret_id is not None:
42
52
  self.vault_secret_id = vault_secret_id
43
53
  self._check_and_renew_vault_client()
44
54
 
45
55
  def _init_vault_client(self):
46
- """Initialize and authenticate the Vault client (imports hvac on demand).
47
-
48
- Args:
49
- vault_secret_id (str): Secret ID for Vault authentication.
50
-
51
- Raises:
52
- ImportError: If hvac package is not installed.
53
- KeyError: If required Vault configuration is missing.
54
- Exception: If Vault authentication fails.
55
- """
56
+ """Initialize and authenticate the Vault client (imports hvac on demand)."""
56
57
  try:
57
58
  import hvac # Lazy import
58
59
  except ImportError as e:
59
60
  raise ImportError(
60
- "hvac package is required for Vault integration. " "Install with: pip install hvac"
61
+ "hvac package is required for Vault integration. Install with: pip install hvac"
61
62
  ) from e
62
63
 
63
64
  try:
@@ -75,84 +76,117 @@ class ConfigLoader(SingletonWithArgs):
75
76
  )
76
77
  except Exception as e:
77
78
  self.vault_client = None
78
- raise Exception(f"Failed to initialize Vault client: {str(e)}")
79
+ raise Exception(f"Failed to initialize Vault client: {str(e)}") from e
79
80
 
80
81
  def _check_and_renew_vault_client(self):
81
- # 检查当前Token的状态,包括过期时间和可续租性
82
82
  if not self.vault_client or not self.vault_client.is_authenticated():
83
- # 如果当前Token无效,则重新认证
84
83
  self._init_vault_client()
85
84
 
85
+ def _is_vault_reference(self, section_config, key_name):
86
+ """检查键是否是Vault引用"""
87
+ return f"{key_name}{self.VAULT_SUFFIX}" in section_config
88
+
86
89
  def get(self, section, key_name, use_cache=True):
87
- """Retrieve a configuration value.
88
-
89
- Args:
90
- section (str): The configuration section name.
91
- key_name (str): The key name within the section.
92
- use_cache (bool): Whether to use cached values. Defaults to True.
93
-
94
- Returns:
95
- The configuration value, which may come from:
96
- - Direct configuration value
97
- - Vault secret (if Vault is initialized)
98
- - Cache (if enabled)
99
-
100
- Raises:
101
- KeyError: If the section or key is not found.
102
- Exception: If Vault access is required but not available.
103
- """
104
- if section not in self._raw_config:
90
+ """Retrieve a configuration value."""
91
+ section_config = self._raw_config.get(section)
92
+ if section_config is None:
105
93
  raise KeyError(f"Section '{section}' not found")
106
94
 
107
- # Check for direct value first
108
- if key_name in self._raw_config[section]:
109
- return self._raw_config[section][key_name]
95
+ if key_name in section_config:
96
+ return section_config[key_name]
110
97
 
111
- # Handle Vault reference if Vault is enabled
112
- vault_key = f"{key_name}@vault"
113
- if vault_key in self._raw_config[section]:
98
+ if self._is_vault_reference(section_config, key_name):
114
99
  if self.vault_client is None:
115
100
  raise Exception(
116
101
  f"Vault access required for {key_name} but Vault is not initialized"
117
102
  )
118
103
 
119
- cache_key = f"{section}_{key_name}"
120
-
104
+ cache_key = f"{section}:{key_name}"
121
105
  if use_cache and cache_key in self.cache:
122
106
  return self.cache[cache_key]
123
107
  value = self._get_value_from_vault(section, key_name)
124
108
  self.cache[cache_key] = value
125
109
  return value
126
110
 
127
- raise KeyError(f"Key '{key_name}' or '{vault_key}' not found in section '{section}'")
128
-
129
- def _get_value_from_vault(self, section, key_name):
130
- """Retrieve a secret value from Vault.
111
+ raise KeyError(f"Key '{key_name}' not found in section '{section}'")
131
112
 
132
- Args:
133
- section (str): The configuration section name.
134
- key_name (str): The key name within the section.
135
-
136
- Returns:
137
- The secret value from Vault.
113
+ def set(self, section, key_name, value, use_cache=True):
114
+ """Set a configuration value to Vault."""
115
+ section_config = self._raw_config.get(section)
116
+ if section_config is None:
117
+ raise KeyError(f"Section '{section}' not found")
118
+
119
+ if not self._is_vault_reference(section_config, key_name):
120
+ raise KeyError(f"Key '{key_name}' is not a Vault reference in section '{section}'")
121
+
122
+ if self.vault_client is None:
123
+ raise Exception(
124
+ f"Vault access required for {key_name} but Vault is not initialized"
125
+ )
126
+
127
+ self._set_value_to_vault(section, key_name, value)
128
+
129
+ cache_key = f"{section}:{key_name}"
130
+ if use_cache:
131
+ self.cache[cache_key] = value
138
132
 
139
- Raises:
140
- Exception: If Vault access fails.
141
- """
133
+ def _get_value_from_vault(self, section, key_name):
134
+ """Retrieve a secret value from Vault."""
142
135
  try:
143
- vault_path = self._raw_config[section]["vault_path"]
144
- vault_key = self._raw_config[section][f"{key_name}@vault"]
145
- vault_namepsace = self._raw_config[section].get("vault_namespace")
146
- if vault_namepsace:
147
- self.vault_client.adapter.namespace = vault_namepsace
148
- else:
149
- self.vault_client.adapter.namespace = self._raw_config["vault"]["space"]
136
+ section_config = self._raw_config[section]
137
+ vault_path = section_config.get("vault_path")
138
+ if not vault_path:
139
+ raise KeyError(f"Missing vault_path in section '{section}'")
140
+
141
+ vault_key_ref = f"{key_name}{self.VAULT_SUFFIX}"
142
+ vault_key = section_config[vault_key_ref]
143
+
144
+ namespace = section_config.get("vault_namespace") or self._raw_config["vault"]["space"]
145
+ self.vault_client.adapter.namespace = namespace
146
+
150
147
  data = self.vault_client.secrets.kv.read_secret_version(
151
- path=vault_path, mount_point="kv", raise_on_deleted_version=True
148
+ path=vault_path, mount_point=self.KV_MOUNT_POINT, raise_on_deleted_version=True
152
149
  )
153
150
  return data["data"]["data"][vault_key]
154
151
  except Exception as e:
155
- raise Exception(f"Failed to fetch {key_name} from Vault: {str(e)}")
152
+ raise Exception(f"Failed to fetch {key_name} from Vault: {str(e)}") from e
153
+
154
+ def _set_value_to_vault(self, section, key_name, value):
155
+ """Set a secret value to Vault."""
156
+ try:
157
+ self._check_and_renew_vault_client()
158
+
159
+ section_config = self._raw_config[section]
160
+ vault_path = section_config.get("vault_path")
161
+ if not vault_path:
162
+ raise KeyError(f"Missing vault_path in section '{section}'")
163
+
164
+ vault_key_ref = f"{key_name}{self.VAULT_SUFFIX}"
165
+ vault_key = section_config[vault_key_ref]
166
+
167
+ namespace = section_config.get("vault_namespace") or self._raw_config["vault"]["space"]
168
+ self.vault_client.adapter.namespace = namespace
169
+
170
+ try:
171
+ data = self.vault_client.secrets.kv.read_secret_version(
172
+ path=vault_path, mount_point=self.KV_MOUNT_POINT, raise_on_deleted_version=True
173
+ )
174
+ secret_data = data["data"]["data"]
175
+ except Exception:
176
+ logger.warning(f"Secret not found, creating new secret at {vault_path}")
177
+ secret_data = {}
178
+
179
+ secret_data[vault_key] = value
180
+
181
+ self.vault_client.secrets.kv.create_or_update_secret(
182
+ path=vault_path,
183
+ secret=secret_data,
184
+ mount_point=self.KV_MOUNT_POINT
185
+ )
186
+
187
+ logger.info(f"Updated Vault secret: {vault_path}/{vault_key}")
188
+ except Exception as e:
189
+ raise Exception(f"Failed to set {key_name} to Vault: {str(e)}") from e
156
190
 
157
191
  def __getitem__(self, section):
158
192
  """Dictionary-style access to configuration sections."""
@@ -160,6 +194,10 @@ class ConfigLoader(SingletonWithArgs):
160
194
  raise KeyError(f"Section '{section}' not found")
161
195
  return SectionProxy(self, section)
162
196
 
197
+ def __setitem__(self, section, proxy):
198
+ """Prevent direct assignment to sections."""
199
+ raise TypeError("ConfigLoader does not support direct section assignment")
200
+
163
201
  def __getattr__(self, section):
164
202
  """Attribute-style access to configuration sections."""
165
203
  try:
@@ -178,6 +216,10 @@ class SectionProxy:
178
216
  def __getitem__(self, key):
179
217
  """Dictionary-style access to configuration values."""
180
218
  return self._loader.get(self._section, key)
219
+
220
+ def __setitem__(self, key, value):
221
+ """Dictionary-style setting of configuration values to Vault."""
222
+ self.set(key, value)
181
223
 
182
224
  def get(self, key, default=None):
183
225
  """Dictionary-style access to configuration values."""
@@ -185,6 +227,10 @@ class SectionProxy:
185
227
  return self._loader.get(self._section, key)
186
228
  except KeyError:
187
229
  return default
230
+
231
+ def set(self, key, value):
232
+ """Set a configuration value to Vault."""
233
+ self._loader.set(self._section, key, value)
188
234
 
189
235
  def __getattr__(self, key):
190
236
  """Attribute-style access to configuration values."""
@@ -192,6 +238,13 @@ class SectionProxy:
192
238
  return self[key]
193
239
  except KeyError as e:
194
240
  raise AttributeError(str(e))
241
+
242
+ def __setattr__(self, name, value):
243
+ """Attribute-style setting of configuration values to Vault."""
244
+ if name.startswith('_'):
245
+ super().__setattr__(name, value)
246
+ else:
247
+ self.set(name, value)
195
248
 
196
249
  def __repr__(self):
197
250
  """String representation of the section's configuration."""
@@ -202,10 +255,40 @@ if __name__ == "__main__":
202
255
  config_without_vault = ConfigLoader("config.yml")
203
256
  print("Without Vault:", config_without_vault.get("jira", "url"))
204
257
 
205
- # This will only work if you provide a valid Vault secret ID
206
- # and hvac package is installed
207
258
  config_with_vault = ConfigLoader("config.yml", vault_secret_id=os.getenv("VAULT_SECRET_ID"))
208
259
 
209
- print("With Vault:", config_with_vault.test.test)
210
- print("With Vault:", config_with_vault["cis"]["cis_client_id"])
260
+ # 属性方式读取
261
+ print("With Vault (attr):", config_with_vault.test.test)
262
+
263
+ # 字典方式读取
264
+ print("With Vault (dict):", config_with_vault["cis"]["cis_client_id"])
265
+
266
+ # 测试不存在的值
211
267
  print("Try val not exists: ", config_with_vault.test.get("not_exists"))
268
+
269
+ # 写入示例 - 属性方式
270
+ try:
271
+ print("Current test value (attr read):", config_with_vault.test.test)
272
+ config_with_vault.test.test = "new_value_123"
273
+ print("After write (attr read):", config_with_vault.test.test)
274
+ except Exception as e:
275
+ print("Attribute write failed:", str(e))
276
+
277
+ # 写入示例 - 字典方式
278
+ try:
279
+ print("Current test value (dict read):", config_with_vault["test"]["test"])
280
+ config_with_vault["test"]["test"] = "new_value_456"
281
+ print("After write (dict read):", config_with_vault["test"]["test"])
282
+
283
+ # 混合方式验证
284
+ print("After write (attr read):", config_with_vault.test.test)
285
+ except Exception as e:
286
+ print("Dictionary write failed:", str(e))
287
+
288
+ # 写入新键示例
289
+ try:
290
+ print("Setting new key...")
291
+ config_with_vault["database"]["new_password"] = "secure123!"
292
+ print("New password:", config_with_vault.database.new_password)
293
+ except Exception as e:
294
+ print("New key write failed:", str(e))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xenoslib
3
- Version: 0.1.30
3
+ Version: 0.2.0
4
4
  Summary: Xenos' common lib
5
5
  Home-page: https://github.com/XenosLu/xenoslib.git
6
6
  Author: Xenocider
@@ -1,6 +1,6 @@
1
1
  xenoslib/__init__.py,sha256=vlMWwdD2gTb9ztpm_orYAm_Y0Be0Es64ifvMQwrE1jU,310
2
2
  xenoslib/__main__.py,sha256=UpD3pl4l5ZUFxK6FzNMWRb1AZpEnwerulSFoPb_Pdgw,307
3
- xenoslib/about.py,sha256=B8RIL1Gxy_xbK_YIIS4rzVETE5QvWJ-XGugxrMrhEpY,229
3
+ xenoslib/about.py,sha256=uFeXGwBMEZ9Wsa7XjcqkicPpYbNWez7gMZ6AunNMQe4,228
4
4
  xenoslib/base.py,sha256=rAXk71AsnWvQ7MaSad5Xxd9Lwucp-cZc15yyqoV5Dk4,12440
5
5
  xenoslib/dev.py,sha256=R6iwKuu-xvaYiBOUyP2gpePyvXss17TOZaCmQEihPCw,1236
6
6
  xenoslib/extend.py,sha256=Vl5d8carSGy6ERE2EjTmB9lOo2uIBWs1iz-fKm_TX-M,5947
@@ -11,9 +11,9 @@ xenoslib/onedrive.py,sha256=-bJJ8Cd_RjJSlDynYqKoZlFKE1HHM34l6NXOQrWOwtg,7783
11
11
  xenoslib/win_trayicon.py,sha256=7GJwX3c2CS1XWQjsyDK5EfM-MmEHdzPJCTX2sGpWmuU,13115
12
12
  xenoslib/windows.py,sha256=lUTD7TowaPqYHgIL6b-GM9PLd-VyJNNGzkzC3K9vOxo,4080
13
13
  xenoslib/tools/__init__.py,sha256=D1qP5tD2ZukXHgjKUh0fFl8jUipM3KaPBZBi0vGPypQ,48
14
- xenoslib/tools/config_loader.py,sha256=K31ksf-cE76aJOqm82ayYIgkwcmk5iAENJNaax5CwNY,7752
15
- xenoslib-0.1.30.dist-info/licenses/LICENSE,sha256=lF6hjufhiR-xAje_Wo7ogxV5phz2d5TgkfL5SGshujg,1066
16
- xenoslib-0.1.30.dist-info/METADATA,sha256=Ve9EhjpGDTtOG91V30a1GW9LGQw356f12uyyfkCsBEo,911
17
- xenoslib-0.1.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
- xenoslib-0.1.30.dist-info/top_level.txt,sha256=Rfm4GdW0NyA2AHDNUF2wFQOfAo53mAPibddSHGd3X60,9
19
- xenoslib-0.1.30.dist-info/RECORD,,
14
+ xenoslib/tools/config_loader.py,sha256=Iuw7DiAb4z2mR3hFsZBQw9YGgGHUJvcNeT_Dj53NahM,11257
15
+ xenoslib-0.2.0.dist-info/licenses/LICENSE,sha256=lF6hjufhiR-xAje_Wo7ogxV5phz2d5TgkfL5SGshujg,1066
16
+ xenoslib-0.2.0.dist-info/METADATA,sha256=QtFP5XxADSObEVeou3ket3GfwWj6374GJqqBHJLPiMc,910
17
+ xenoslib-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ xenoslib-0.2.0.dist-info/top_level.txt,sha256=Rfm4GdW0NyA2AHDNUF2wFQOfAo53mAPibddSHGd3X60,9
19
+ xenoslib-0.2.0.dist-info/RECORD,,