xenfra 0.3.5__py3-none-any.whl → 0.3.7__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.
xenfra/utils/config.py CHANGED
@@ -29,18 +29,8 @@ def read_xenfra_yaml(filename: str = "xenfra.yaml") -> dict:
29
29
  Raises:
30
30
  FileNotFoundError: If the config file doesn't exist
31
31
  yaml.YAMLError: If the YAML is malformed
32
- """
33
- """
34
- Read and parse xenfra.yaml configuration file.
35
-
36
- Args:
37
- filename: Path to the config file (default: xenfra.yaml)
38
-
39
- Returns:
40
- Dictionary containing the configuration
41
-
42
- Raises:
43
- FileNotFoundError: If the config file doesn't exist
32
+ ValueError: If the YAML is invalid
33
+ IOError: If reading fails
44
34
  """
45
35
  if not Path(filename).exists():
46
36
  raise FileNotFoundError(
@@ -162,17 +152,7 @@ def apply_patch(patch: dict, target_file: str = None, create_backup_file: bool =
162
152
  raise ValueError(
163
153
  f"Invalid patch operation: {operation}. Must be 'add', 'replace', or 'remove'"
164
154
  )
165
- """
166
- Apply a JSON patch to a configuration file with automatic backup.
167
155
 
168
- Args:
169
- patch: Patch object with file, operation, path, value
170
- target_file: Optional override for the file to patch
171
- create_backup_file: Whether to create a backup before patching (default: True)
172
-
173
- Returns:
174
- Path to the backup file if created, None otherwise
175
- """
176
156
  file_to_patch = target_file or patch.get("file")
177
157
 
178
158
  if not file_to_patch:
@@ -193,7 +173,7 @@ def apply_patch(patch: dict, target_file: str = None, create_backup_file: bool =
193
173
 
194
174
  # Apply patch based on operation
195
175
  operation = patch.get("operation")
196
- path = patch.get("path", "").strip("/")
176
+ path = (patch.get("path") or "").strip("/")
197
177
  value = patch.get("value")
198
178
 
199
179
  if operation == "add":
@@ -235,7 +215,7 @@ def apply_patch(patch: dict, target_file: str = None, create_backup_file: bool =
235
215
  config_data = json.load(f)
236
216
 
237
217
  operation = patch.get("operation")
238
- path = patch.get("path", "").strip("/")
218
+ path = (patch.get("path") or "").strip("/")
239
219
  value = patch.get("value")
240
220
 
241
221
  if operation == "add":
@@ -280,6 +260,68 @@ def apply_patch(patch: dict, target_file: str = None, create_backup_file: bool =
280
260
  # Replace entire file
281
261
  with open(file_to_patch, "w") as f:
282
262
  f.write(str(value))
263
+
264
+ # For TOML files (pyproject.toml)
265
+ elif file_to_patch.endswith(".toml"):
266
+ import toml
267
+
268
+ with open(file_to_patch, "r") as f:
269
+ config_data = toml.load(f)
270
+
271
+ operation = patch.get("operation")
272
+ path = (patch.get("path") or "").strip("/")
273
+ value = patch.get("value")
274
+
275
+ if operation == "add":
276
+ # Special case for pyproject.toml dependencies
277
+ is_pyproject = os.path.basename(file_to_patch) == "pyproject.toml"
278
+ if is_pyproject and (not path or path == "project/dependencies"):
279
+ # Ensure project and dependencies exist
280
+ if "project" not in config_data:
281
+ config_data["project"] = {}
282
+ if "dependencies" not in config_data["project"]:
283
+ config_data["project"]["dependencies"] = []
284
+
285
+ # Add value if not already present
286
+ if value not in config_data["project"]["dependencies"]:
287
+ config_data["project"]["dependencies"].append(value)
288
+ elif path:
289
+ path_parts = path.split("/")
290
+ current = config_data
291
+ for part in path_parts[:-1]:
292
+ if part not in current:
293
+ current[part] = {}
294
+ current = current[part]
295
+
296
+ # If target is a list (like dependencies), append
297
+ target_key = path_parts[-1]
298
+ if target_key in current and isinstance(current[target_key], list):
299
+ if value not in current[target_key]:
300
+ current[target_key].append(value)
301
+ else:
302
+ current[target_key] = value
303
+ else:
304
+ # Root level add
305
+ if isinstance(value, dict):
306
+ config_data.update(value)
307
+ else:
308
+ # Ignore root-level non-dict adds for structured files
309
+ # to prevent overwriting the entire config with a string
310
+ pass
311
+
312
+ elif operation == "replace":
313
+ if path:
314
+ path_parts = path.split("/")
315
+ current = config_data
316
+ for part in path_parts[:-1]:
317
+ current = current[part]
318
+ current[path_parts[-1]] = value
319
+ else:
320
+ config_data = value
321
+
322
+ # Write back
323
+ with open(file_to_patch, "w") as f:
324
+ toml.dump(config_data, f)
283
325
  else:
284
326
  # Design decision: Only support auto-patching for common dependency files
285
327
  # Other file types should be manually edited to avoid data loss
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: xenfra
3
- Version: 0.3.5
3
+ Version: 0.3.7
4
4
  Summary: A 'Zen Mode' infrastructure engine for Python developers.
5
5
  Author: xenfra-cloud
6
6
  Author-email: xenfra-cloud <xenfracloud@gmail.com>
@@ -24,6 +24,7 @@ Requires-Dist: keyring>=25.7.0
24
24
  Requires-Dist: keyrings-alt>=5.0.2
25
25
  Requires-Dist: tenacity>=8.2.3
26
26
  Requires-Dist: cryptography>=43.0.0
27
+ Requires-Dist: toml>=0.10.2
27
28
  Requires-Dist: pytest>=8.0.0 ; extra == 'test'
28
29
  Requires-Dist: pytest-mock>=3.12.0 ; extra == 'test'
29
30
  Requires-Python: >=3.13
@@ -10,11 +10,11 @@ xenfra/main.py,sha256=2EPPuIdxjhW-I-e-Mc0i2ayeLaSJdmzddNThkXq7B7c,2033
10
10
  xenfra/utils/__init__.py,sha256=4ZRYkrb--vzoXjBHG8zRxz2jCXNGtAoKNtkyu2WRI2A,45
11
11
  xenfra/utils/auth.py,sha256=9JbFnv0-rdlJF-4hKD2uWd9h9ehqC1iIHee1O5e-3RM,13769
12
12
  xenfra/utils/codebase.py,sha256=GMrqhOJWX8q5ZXSLI9P3hJZBpufXMQA3Z4fKh2XSTNo,5949
13
- xenfra/utils/config.py,sha256=F2zedd3JXP7TBdul0u8b4NVx-C1N6Hq4sH5szyWim6M,11947
13
+ xenfra/utils/config.py,sha256=BylyzHLkL6rmvNNW9zxCaSvk1dV0yzJQPaW-dP7E5j0,13931
14
14
  xenfra/utils/errors.py,sha256=6G91YzzDDNkKHANTgfAMiOiMElEyi57wo6-FzRa4VuQ,4211
15
15
  xenfra/utils/security.py,sha256=EA8CIPLt8Y-QP5uZ7c5NuC6ZLRV1aZS8NapS9ix_vok,11479
16
16
  xenfra/utils/validation.py,sha256=cvuL_AEFJ2oCoP0abCqoOIABOwz79Gkf-jh_dcFIQlM,6912
17
- xenfra-0.3.5.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
18
- xenfra-0.3.5.dist-info/entry_points.txt,sha256=a_2cGhYK__X6eW05Ba8uB6RIM_61c2sHtXsPY8N0mic,45
19
- xenfra-0.3.5.dist-info/METADATA,sha256=OrFNS_krWEq8-BglAdLn5A1Lpel4bxWcpDcqDcsmrRk,3870
20
- xenfra-0.3.5.dist-info/RECORD,,
17
+ xenfra-0.3.7.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
18
+ xenfra-0.3.7.dist-info/entry_points.txt,sha256=a_2cGhYK__X6eW05Ba8uB6RIM_61c2sHtXsPY8N0mic,45
19
+ xenfra-0.3.7.dist-info/METADATA,sha256=P2qLV0wwwrioPQtkXda94RA7FUOBSmKmzOooQCNiD8Q,3898
20
+ xenfra-0.3.7.dist-info/RECORD,,
File without changes