quick-ini 3.1.4__tar.gz → 3.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quick_ini
3
- Version: 3.1.4
3
+ Version: 3.2
4
4
  Summary: A library for reading from and writing to .ini formatted files
5
5
  Home-page: https://github.com/olliez-mods/Quick-Ini
6
6
  Author: OllieZ-Mods
@@ -1,9 +1,9 @@
1
1
  from urllib.parse import urlparse
2
- from typing import Type, Any, Union, Dict, Optional
2
+ from typing import Type, Any, Union, Dict, Optional, List
3
3
  import os
4
4
 
5
5
  class QuickIni:
6
- parsed_ini: Dict[str, Union[str, int, bool, float, None]] = {}
6
+ parsed_ini: Dict[str, Union[str, int, bool, float, List, None]] = {}
7
7
  loaded_file_path: Optional[str] = None
8
8
  auto_type_convert_g: bool = True
9
9
  error_message: str = ""
@@ -101,15 +101,45 @@ class QuickIni:
101
101
  return False
102
102
 
103
103
  lines = f_data.splitlines()
104
+ i = 0
104
105
 
105
- for line in lines:
106
- if(line.startswith("#") or "=" not in line): continue # Ignore comments and lines without "="
106
+ while i < len(lines):
107
+ line = lines[i]
108
+
109
+ # Skip comments and lines without "="
110
+ if(line.startswith("#") or "=" not in line):
111
+ i += 1
112
+ continue
113
+
107
114
  left, right = line.split("=", 1)
108
- if(not right): right = empty_default
109
- elif(not auto_type_convert): pass # If we aren't auto converting, skip the conversion
110
- else: right = QuickIni.string_to_type(right)
115
+
116
+ # Check if this is an array (empty value and next line starts with |)
117
+ if not right.strip() and i + 1 < len(lines) and lines[i + 1].startswith("|"):
118
+ # This is an array, collect all array items
119
+ array_items = []
120
+ i += 1 # Move to the first array item
121
+
122
+ while i < len(lines) and lines[i].startswith("|"):
123
+ array_value = lines[i][1:] # Remove the | prefix
124
+
125
+ if auto_type_convert:
126
+ array_value = QuickIni.string_to_type(array_value)
127
+
128
+ array_items.append(array_value)
129
+ i += 1
130
+
131
+ QuickIni.parsed_ini[left] = array_items
132
+ else:
133
+ # Regular key-value pair
134
+ if(not right):
135
+ right = empty_default
136
+ elif(not auto_type_convert):
137
+ pass # If we aren't auto converting, skip the conversion
138
+ else:
139
+ right = QuickIni.string_to_type(right)
111
140
 
112
- QuickIni.parsed_ini[left] = right
141
+ QuickIni.parsed_ini[left] = right
142
+ i += 1
113
143
 
114
144
  QuickIni.loaded_file_path=file_location
115
145
  return True
@@ -142,7 +172,7 @@ class QuickIni:
142
172
 
143
173
  Parameters:
144
174
  key (str): The key to update or add in the .ini file.
145
- value (Any): The value to write, which will be converted to a string.
175
+ value (Any): The value to write, which will be converted to a string. If value is a list, it will be written as an array.
146
176
  add_if_not_found (bool): If True, adds the key-value pair if the key is not found in the file (default is False).
147
177
  update_localy (bool): If True, updates the parsed_ini dictionary locally with the new key-value pair (default is True).
148
178
  do_backup (bool): If True, creates a backup of the original file before making changes, and removes if successful.
@@ -159,18 +189,25 @@ class QuickIni:
159
189
  Example:
160
190
  >>> write_value("example_key", 42, add_if_not_found=True)
161
191
  42
192
+ >>> write_value("parts", ["one", "two", "three"], add_if_not_found=True)
193
+ ["one", "two", "three"]
162
194
  """
163
195
 
164
196
  if(not QuickIni.loaded_file_path):
165
197
  QuickIni.error_message = "No loaded file path, was a local file loaded?"
166
198
  raise(ValueError(QuickIni.error_message))
167
199
 
168
- try: str(value)
169
- except(TypeError, ValueError):
170
- QuickIni.error_message = "Could not convert value to a string"
171
- raise(ValueError(QuickIni.error_message))
172
-
173
- key_value_pair = f"{str(key)}={str(value)}\n"
200
+ # Handle array values
201
+ if isinstance(value, list):
202
+ key_value_pair = f"{str(key)}=\n"
203
+ for item in value:
204
+ key_value_pair += f"|{str(item)}\n"
205
+ else:
206
+ try: str(value)
207
+ except(TypeError, ValueError):
208
+ QuickIni.error_message = "Could not convert value to a string"
209
+ raise(ValueError(QuickIni.error_message))
210
+ key_value_pair = f"{str(key)}={str(value)}\n"
174
211
 
175
212
  try:
176
213
  file = open(str(QuickIni.loaded_file_path), 'r', encoding='utf-8')
@@ -185,25 +222,37 @@ class QuickIni:
185
222
  print("Backup created at", f"{str(QuickIni.loaded_file_path)}.backup")
186
223
 
187
224
  key_found=False
188
- last_line=""
189
- file = open(str(QuickIni.loaded_file_path), 'w')
190
- for line in lines:
225
+ new_lines = []
226
+ i = 0
227
+
228
+ while i < len(lines):
229
+ line = lines[i]
230
+
191
231
  if(not line.startswith("#") and not key_found and line.startswith(f"{key}=")):
192
- last_line = key_value_pair
232
+ # Found the key, replace it and skip any existing array items
233
+ new_lines.append(key_value_pair)
193
234
  key_found = True
235
+ i += 1
236
+
237
+ # Skip existing array items if they exist
238
+ while i < len(lines) and lines[i].startswith("|"):
239
+ i += 1
194
240
  else:
195
- last_line = line
196
- file.write(last_line)
241
+ new_lines.append(line)
242
+ i += 1
197
243
 
198
244
  if(not key_found):
199
245
  if(add_if_not_found):
200
- if(not last_line.endswith("\n")):
201
- file.write("\n")
202
- file.write(f"{key_value_pair}")
246
+ if new_lines and not new_lines[-1].endswith("\n"):
247
+ new_lines.append("\n")
248
+ new_lines.append(key_value_pair)
203
249
  else:
204
250
  QuickIni.error_message = f"Could not find key '{key}'"
205
251
  raise(ValueError(QuickIni.error_message))
206
252
 
253
+ # Write the modified content
254
+ file = open(str(QuickIni.loaded_file_path), 'w')
255
+ file.writelines(new_lines)
207
256
  file.close()
208
257
 
209
258
  except FileNotFoundError as e:
@@ -216,7 +265,12 @@ class QuickIni:
216
265
  QuickIni.error_message = f"IOError when reading or writting to the file '{QuickIni.loaded_file_path}'"
217
266
  raise(e)
218
267
 
219
- v = QuickIni.string_to_type(str(value)) if QuickIni.auto_type_convert_g else str(value)
268
+ # Handle return value
269
+ if isinstance(value, list):
270
+ v = [QuickIni.string_to_type(str(item)) if QuickIni.auto_type_convert_g else str(item) for item in value]
271
+ else:
272
+ v = QuickIni.string_to_type(str(value)) if QuickIni.auto_type_convert_g else str(value)
273
+
220
274
  if(update_locally):
221
275
  QuickIni.parsed_ini[str(key)] = v
222
276
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quick_ini
3
- Version: 3.1.4
3
+ Version: 3.2
4
4
  Summary: A library for reading from and writing to .ini formatted files
5
5
  Home-page: https://github.com/olliez-mods/Quick-Ini
6
6
  Author: OllieZ-Mods
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="quick_ini",
8
- version="3.1.4",
8
+ version="3.2",
9
9
  description="A library for reading from and writing to .ini formatted files",
10
10
  long_description=long_description,
11
11
  long_description_content_type="text/markdown",
@@ -0,0 +1,467 @@
1
+ #
2
+ # WRITTEN ENTIRELY BY CHATGPT
3
+ # I DON'T TRUST IT
4
+ #
5
+
6
+ import unittest
7
+ import tempfile
8
+ import os
9
+ from unittest.mock import patch, mock_open
10
+ from quick_ini import QuickIni
11
+
12
+
13
+ class TestQuickIni(unittest.TestCase):
14
+
15
+ def setUp(self):
16
+ """Set up test fixtures before each test method."""
17
+ # Clear any previous state
18
+ QuickIni.parsed_ini = {}
19
+ QuickIni.loaded_file_path = None
20
+ QuickIni.auto_type_convert_g = True
21
+ QuickIni.error_message = ""
22
+ QuickIni.web_file = None
23
+
24
+ # Create a temporary INI file for testing
25
+ self.test_content = """# Test configuration
26
+ debug=true
27
+ port=8080
28
+ timeout=30.5
29
+ name=TestApp
30
+ empty_value=
31
+ false_value=false
32
+
33
+ # Array example
34
+ parts=
35
+ |one
36
+ |two
37
+ |three
38
+
39
+ # Mixed array
40
+ mixed=
41
+ |text
42
+ |42
43
+ |3.14
44
+ |true
45
+ |false
46
+
47
+ # Comments and edge cases
48
+ # This is a comment
49
+ another_key=value with spaces
50
+ """
51
+ self.temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.ini', delete=False)
52
+ self.temp_file.write(self.test_content)
53
+ self.temp_file.close()
54
+
55
+ def tearDown(self):
56
+ """Clean up after each test method."""
57
+ if os.path.exists(self.temp_file.name):
58
+ os.unlink(self.temp_file.name)
59
+
60
+ # ===== BASIC LOADING TESTS =====
61
+
62
+ def test_load_file_success(self):
63
+ """Test loading an INI file successfully."""
64
+ result = QuickIni.load_file(self.temp_file.name)
65
+ self.assertTrue(result)
66
+ self.assertEqual(QuickIni.loaded_file_path, self.temp_file.name)
67
+
68
+ def test_load_file_nonexistent(self):
69
+ """Test loading a nonexistent file."""
70
+ result = QuickIni.load_file("nonexistent_file.ini")
71
+ self.assertFalse(result)
72
+ error = QuickIni.get_last_error()
73
+ self.assertIn("Could not find file", error)
74
+
75
+ def test_load_file_with_auto_type_convert_disabled(self):
76
+ """Test loading with auto type conversion disabled."""
77
+ result = QuickIni.load_file(self.temp_file.name, auto_type_convert=False)
78
+ self.assertTrue(result)
79
+ # All values should be strings when auto_type_convert is False
80
+ self.assertEqual(QuickIni.get_value("port"), "8080")
81
+ self.assertEqual(QuickIni.get_value("debug"), "true")
82
+ self.assertEqual(QuickIni.get_value("timeout"), "30.5")
83
+
84
+ def test_load_file_with_empty_default(self):
85
+ """Test loading with custom empty default value."""
86
+ result = QuickIni.load_file(self.temp_file.name, empty_default="EMPTY")
87
+ self.assertTrue(result)
88
+ self.assertEqual(QuickIni.get_value("empty_value"), "EMPTY")
89
+
90
+ # ===== TYPE CONVERSION TESTS =====
91
+
92
+ def test_string_to_type_integer(self):
93
+ """Test string to integer conversion."""
94
+ self.assertEqual(QuickIni.string_to_type("123"), 123)
95
+ self.assertEqual(QuickIni.string_to_type("0"), 0)
96
+
97
+ def test_string_to_type_float(self):
98
+ """Test string to float conversion."""
99
+ self.assertEqual(QuickIni.string_to_type("123.45"), 123.45)
100
+ self.assertEqual(QuickIni.string_to_type("0.0"), 0.0)
101
+
102
+ def test_string_to_type_boolean(self):
103
+ """Test string to boolean conversion."""
104
+ self.assertEqual(QuickIni.string_to_type("true"), True)
105
+ self.assertEqual(QuickIni.string_to_type("True"), True)
106
+ self.assertEqual(QuickIni.string_to_type("TRUE"), True)
107
+ self.assertEqual(QuickIni.string_to_type("false"), False)
108
+ self.assertEqual(QuickIni.string_to_type("False"), False)
109
+ self.assertEqual(QuickIni.string_to_type("FALSE"), False)
110
+
111
+ def test_string_to_type_string(self):
112
+ """Test string remains string when not convertible."""
113
+ self.assertEqual(QuickIni.string_to_type("hello"), "hello")
114
+ self.assertEqual(QuickIni.string_to_type("123abc"), "123abc")
115
+ self.assertEqual(QuickIni.string_to_type("12.34.56"), "12.34.56")
116
+
117
+ # ===== GET VALUE TESTS =====
118
+
119
+ def test_get_value_string(self):
120
+ """Test getting string values."""
121
+ QuickIni.load_file(self.temp_file.name)
122
+ value = QuickIni.get_value("name")
123
+ self.assertEqual(value, "TestApp")
124
+ self.assertIsInstance(value, str)
125
+
126
+ def test_get_value_boolean_true(self):
127
+ """Test getting boolean true values."""
128
+ QuickIni.load_file(self.temp_file.name)
129
+ value = QuickIni.get_value("debug")
130
+ self.assertTrue(value)
131
+ self.assertIsInstance(value, bool)
132
+
133
+ def test_get_value_boolean_false(self):
134
+ """Test getting boolean false values."""
135
+ QuickIni.load_file(self.temp_file.name)
136
+ value = QuickIni.get_value("false_value")
137
+ self.assertFalse(value)
138
+ self.assertIsInstance(value, bool)
139
+
140
+ def test_get_value_integer(self):
141
+ """Test getting integer values."""
142
+ QuickIni.load_file(self.temp_file.name)
143
+ value = QuickIni.get_value("port")
144
+ self.assertEqual(value, 8080)
145
+ self.assertIsInstance(value, int)
146
+
147
+ def test_get_value_float(self):
148
+ """Test getting float values."""
149
+ QuickIni.load_file(self.temp_file.name)
150
+ value = QuickIni.get_value("timeout")
151
+ self.assertEqual(value, 30.5)
152
+ self.assertIsInstance(value, float)
153
+
154
+ def test_get_value_default(self):
155
+ """Test getting default values for missing keys."""
156
+ QuickIni.load_file(self.temp_file.name)
157
+ value = QuickIni.get_value("missing_key", "default")
158
+ self.assertEqual(value, "default")
159
+
160
+ def test_get_value_with_type_checking(self):
161
+ """Test get_value with expected_type parameter."""
162
+ QuickIni.load_file(self.temp_file.name)
163
+
164
+ # Valid type checks should pass
165
+ value = QuickIni.get_value("port", expected_type=int)
166
+ self.assertEqual(value, 8080)
167
+
168
+ value = QuickIni.get_value("name", expected_type=str)
169
+ self.assertEqual(value, "TestApp")
170
+
171
+ # Invalid type checks should raise ValueError
172
+ with self.assertRaises(ValueError):
173
+ QuickIni.get_value("port", expected_type=str)
174
+
175
+ def test_get_value_spaces_in_value(self):
176
+ """Test getting values with spaces."""
177
+ QuickIni.load_file(self.temp_file.name)
178
+ value = QuickIni.get_value("another_key")
179
+ self.assertEqual(value, "value with spaces")
180
+
181
+ # ===== ARRAY TESTS =====
182
+
183
+ def test_get_array_value(self):
184
+ """Test getting array values."""
185
+ QuickIni.load_file(self.temp_file.name)
186
+ parts = QuickIni.get_value("parts")
187
+ self.assertEqual(parts, ["one", "two", "three"])
188
+ self.assertIsInstance(parts, list)
189
+
190
+ def test_get_mixed_array_with_type_conversion(self):
191
+ """Test getting mixed type arrays with automatic conversion."""
192
+ QuickIni.load_file(self.temp_file.name)
193
+ mixed = QuickIni.get_value("mixed")
194
+ self.assertEqual(mixed[0], "text")
195
+ self.assertEqual(mixed[1], 42)
196
+ self.assertEqual(mixed[2], 3.14)
197
+ self.assertEqual(mixed[3], True)
198
+ self.assertEqual(mixed[4], False)
199
+
200
+ def test_array_without_type_conversion(self):
201
+ """Test arrays without automatic type conversion."""
202
+ QuickIni.load_file(self.temp_file.name, auto_type_convert=False)
203
+ mixed = QuickIni.get_value("mixed")
204
+ # All should be strings
205
+ self.assertEqual(mixed, ["text", "42", "3.14", "true", "false"])
206
+
207
+ def test_write_array_value(self):
208
+ """Test writing array values to file."""
209
+ QuickIni.load_file(self.temp_file.name)
210
+ test_array = ["apple", "banana", "cherry"]
211
+ result = QuickIni.write_value("fruits", test_array, add_if_not_found=True)
212
+ self.assertEqual(result, test_array)
213
+
214
+ # Verify it was written as an array
215
+ fruits = QuickIni.get_value("fruits")
216
+ self.assertEqual(fruits, test_array)
217
+ self.assertIsInstance(fruits, list)
218
+
219
+ def test_write_mixed_array(self):
220
+ """Test writing mixed type arrays."""
221
+ QuickIni.load_file(self.temp_file.name)
222
+ mixed_array = ["text", 42, 3.14, True, False]
223
+ QuickIni.write_value("new_mixed", mixed_array, add_if_not_found=True)
224
+
225
+ # Reload to test parsing
226
+ QuickIni.load_file(self.temp_file.name)
227
+ result = QuickIni.get_value("new_mixed")
228
+ self.assertEqual(result, mixed_array)
229
+
230
+ def test_replace_existing_array(self):
231
+ """Test replacing an existing array."""
232
+ QuickIni.load_file(self.temp_file.name)
233
+ original_parts = QuickIni.get_value("parts")
234
+ self.assertEqual(original_parts, ["one", "two", "three"])
235
+
236
+ # Replace with new array
237
+ new_parts = ["alpha", "beta", "gamma"]
238
+ QuickIni.write_value("parts", new_parts)
239
+
240
+ # Verify replacement
241
+ updated_parts = QuickIni.get_value("parts")
242
+ self.assertEqual(updated_parts, new_parts)
243
+
244
+ # ===== WRITE VALUE TESTS =====
245
+
246
+ def test_write_value_new_key(self):
247
+ """Test writing new key-value pairs."""
248
+ QuickIni.load_file(self.temp_file.name)
249
+ result = QuickIni.write_value("new_key", "new_value", add_if_not_found=True)
250
+ self.assertEqual(result, "new_value")
251
+
252
+ # Verify it was written
253
+ value = QuickIni.get_value("new_key")
254
+ self.assertEqual(value, "new_value")
255
+
256
+ def test_write_value_existing_key(self):
257
+ """Test updating existing key-value pairs."""
258
+ QuickIni.load_file(self.temp_file.name)
259
+ # Update existing value
260
+ result = QuickIni.write_value("name", "UpdatedApp")
261
+ self.assertEqual(result, "UpdatedApp")
262
+
263
+ # Verify it was updated
264
+ value = QuickIni.get_value("name")
265
+ self.assertEqual(value, "UpdatedApp")
266
+
267
+ def test_write_value_key_not_found_without_add_flag(self):
268
+ """Test writing to non-existent key without add_if_not_found flag."""
269
+ QuickIni.load_file(self.temp_file.name)
270
+ with self.assertRaises(ValueError) as context:
271
+ QuickIni.write_value("nonexistent_key", "value")
272
+ self.assertIn("Could not find key", str(context.exception))
273
+
274
+ def test_write_value_no_file_loaded(self):
275
+ """Test writing when no file is loaded."""
276
+ with self.assertRaises(ValueError) as context:
277
+ QuickIni.write_value("key", "value")
278
+ self.assertIn("No loaded file path", str(context.exception))
279
+
280
+ def test_write_value_with_backup(self):
281
+ """Test writing with backup functionality."""
282
+ QuickIni.load_file(self.temp_file.name)
283
+ backup_path = f"{self.temp_file.name}.backup"
284
+
285
+ # Ensure backup doesn't exist initially
286
+ if os.path.exists(backup_path):
287
+ os.remove(backup_path)
288
+
289
+ QuickIni.write_value("name", "BackupTest", do_backup=True)
290
+
291
+ # Backup should be created and then removed
292
+ self.assertFalse(os.path.exists(backup_path))
293
+
294
+ def test_write_value_update_locally_false(self):
295
+ """Test writing without updating local dictionary."""
296
+ QuickIni.load_file(self.temp_file.name)
297
+ original_value = QuickIni.get_value("name")
298
+
299
+ QuickIni.write_value("name", "NotUpdatedLocally", update_locally=False)
300
+
301
+ # Local value should remain unchanged
302
+ current_value = QuickIni.get_value("name")
303
+ self.assertEqual(current_value, original_value)
304
+
305
+ # ===== URL FUNCTIONALITY TESTS =====
306
+
307
+ def test_is_string_url_valid(self):
308
+ """Test URL validation with valid URLs."""
309
+ self.assertTrue(QuickIni.is_string_url("http://example.com"))
310
+ self.assertTrue(QuickIni.is_string_url("https://example.com"))
311
+ self.assertTrue(QuickIni.is_string_url("ftp://example.com"))
312
+
313
+ def test_is_string_url_invalid(self):
314
+ """Test URL validation with invalid URLs."""
315
+ self.assertFalse(QuickIni.is_string_url("not_a_url"))
316
+ self.assertFalse(QuickIni.is_string_url("file.txt"))
317
+ self.assertFalse(QuickIni.is_string_url(""))
318
+
319
+ @patch('urllib.request.urlopen')
320
+ def test_grab_from_url_success_with_different_status(self, mock_urlopen):
321
+ """Test successful URL loading with status 200."""
322
+ # Mock successful HTTP response with explicit status check
323
+ mock_response = mock_urlopen.return_value.__enter__.return_value
324
+ mock_response.status = 200
325
+ mock_response.read.return_value = b"status_test=success"
326
+
327
+ result = QuickIni.grab_from_url("http://example.com/status.ini")
328
+ self.assertTrue(result)
329
+ self.assertEqual(QuickIni.web_file, "status_test=success")
330
+
331
+ @patch('urllib.request.urlopen')
332
+ def test_grab_from_url_non_200_status(self, mock_urlopen):
333
+ """Test URL loading with non-200 status code."""
334
+ # Mock non-200 HTTP response
335
+ mock_response = mock_urlopen.return_value.__enter__.return_value
336
+ mock_response.status = 500
337
+
338
+ result = QuickIni.grab_from_url("http://example.com/error.ini")
339
+ self.assertFalse(result)
340
+ self.assertIn("Failed to download file", QuickIni.get_last_error())
341
+ self.assertIn("got response: 500", QuickIni.get_last_error())
342
+
343
+ @patch('urllib.request.urlopen')
344
+ def test_grab_from_url_http_error_exception(self, mock_urlopen):
345
+ """Test URL loading with HTTPError exception."""
346
+ from urllib.error import HTTPError
347
+ # Mock HTTPError exception
348
+ mock_urlopen.side_effect = HTTPError(None, 404, "Not Found", None, None)
349
+
350
+ result = QuickIni.grab_from_url("http://example.com/notfound.ini")
351
+ self.assertFalse(result)
352
+ self.assertIn("Could not get file from URL", QuickIni.get_last_error())
353
+
354
+ def test_is_string_url_value_error(self):
355
+ """Test URL validation with malformed URL that raises ValueError."""
356
+ # Test with a string that causes urlparse to raise ValueError
357
+ result = QuickIni.is_string_url("ht tp://invalid url")
358
+ self.assertFalse(result)
359
+
360
+ def test_write_value_permission_error(self):
361
+ """Test write_value with permission errors."""
362
+ QuickIni.load_file(self.temp_file.name)
363
+
364
+ # Mock file operations to raise PermissionError
365
+ with patch('builtins.open', side_effect=PermissionError("Permission denied")):
366
+ with self.assertRaises(PermissionError):
367
+ QuickIni.write_value("test_key", "test_value")
368
+
369
+ def test_write_value_io_error(self):
370
+ """Test write_value with I/O errors."""
371
+ QuickIni.load_file(self.temp_file.name)
372
+
373
+ # Mock file operations to raise IOError
374
+ with patch('builtins.open', side_effect=IOError("I/O error")):
375
+ with self.assertRaises(IOError):
376
+ QuickIni.write_value("test_key", "test_value")
377
+
378
+ def test_write_value_backup_removal_failure(self):
379
+ """Test backup removal failure handling."""
380
+ QuickIni.load_file(self.temp_file.name)
381
+
382
+ # Mock os.remove to raise an exception
383
+ with patch('os.remove', side_effect=OSError("Cannot remove backup")):
384
+ with self.assertRaises(OSError):
385
+ QuickIni.write_value("test_key", "test_value", add_if_not_found=True, do_backup=True)
386
+
387
+ @patch('urllib.request.urlopen')
388
+ def test_load_file_from_url(self, mock_urlopen):
389
+ """Test loading INI file from URL."""
390
+ # Mock successful HTTP response
391
+ mock_response = mock_urlopen.return_value.__enter__.return_value
392
+ mock_response.status = 200
393
+ mock_response.read.return_value = b"url_key=url_value\nurl_port=9090"
394
+
395
+ result = QuickIni.load_file("http://example.com/config.ini")
396
+ self.assertTrue(result)
397
+ self.assertEqual(QuickIni.get_value("url_key"), "url_value")
398
+ self.assertEqual(QuickIni.get_value("url_port"), 9090)
399
+
400
+ # ===== ERROR HANDLING TESTS =====
401
+
402
+ def test_get_last_error(self):
403
+ """Test error message retrieval."""
404
+ # Trigger an error
405
+ QuickIni.load_file("nonexistent_file.ini")
406
+ error = QuickIni.get_last_error()
407
+ self.assertIsInstance(error, str)
408
+ self.assertTrue(len(error) > 0)
409
+
410
+ def test_file_io_error_handling(self):
411
+ """Test handling of file I/O errors."""
412
+ # Test with invalid file path
413
+ result = QuickIni.load_file("/invalid/path/file.ini")
414
+ self.assertFalse(result)
415
+ error = QuickIni.get_last_error()
416
+ self.assertIn("Could not find file", error)
417
+
418
+ # ===== EDGE CASES =====
419
+
420
+ def test_empty_ini_file(self):
421
+ """Test loading empty INI file."""
422
+ empty_file = tempfile.NamedTemporaryFile(mode='w', suffix='.ini', delete=False)
423
+ empty_file.write("")
424
+ empty_file.close()
425
+
426
+ try:
427
+ result = QuickIni.load_file(empty_file.name)
428
+ self.assertTrue(result)
429
+ # Should have no parsed values
430
+ self.assertEqual(len(QuickIni.parsed_ini), 0)
431
+ finally:
432
+ os.unlink(empty_file.name)
433
+
434
+ def test_comments_only_file(self):
435
+ """Test loading file with only comments."""
436
+ comments_file = tempfile.NamedTemporaryFile(mode='w', suffix='.ini', delete=False)
437
+ comments_file.write("# Comment 1\n# Comment 2\n# Comment 3")
438
+ comments_file.close()
439
+
440
+ try:
441
+ result = QuickIni.load_file(comments_file.name)
442
+ self.assertTrue(result)
443
+ # Should have no parsed values
444
+ self.assertEqual(len(QuickIni.parsed_ini), 0)
445
+ finally:
446
+ os.unlink(comments_file.name)
447
+
448
+ def test_malformed_lines(self):
449
+ """Test handling of malformed lines without equals signs."""
450
+ malformed_file = tempfile.NamedTemporaryFile(mode='w', suffix='.ini', delete=False)
451
+ malformed_file.write("valid_key=valid_value\nmalformed_line_no_equals\nanother_valid=value")
452
+ malformed_file.close()
453
+
454
+ try:
455
+ result = QuickIni.load_file(malformed_file.name)
456
+ self.assertTrue(result)
457
+ # Should only have valid lines
458
+ self.assertEqual(QuickIni.get_value("valid_key"), "valid_value")
459
+ self.assertEqual(QuickIni.get_value("another_valid"), "value")
460
+ # Malformed line should be ignored
461
+ self.assertIsNone(QuickIni.get_value("malformed_line_no_equals"))
462
+ finally:
463
+ os.unlink(malformed_file.name)
464
+
465
+
466
+ if __name__ == '__main__':
467
+ unittest.main()
@@ -1,91 +0,0 @@
1
- import unittest
2
- import tempfile
3
- import os
4
- from quick_ini import QuickIni
5
-
6
-
7
- class TestQuickIni(unittest.TestCase):
8
-
9
- def setUp(self):
10
- """Set up test fixtures before each test method."""
11
- # Create a temporary INI file for testing
12
- self.test_content = """# Test configuration
13
- debug=true
14
- port=8080
15
- timeout=30.5
16
- name=TestApp
17
- empty_value=
18
-
19
- # Array example
20
- parts=
21
- |one
22
- |two
23
- |three
24
- """
25
- self.temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.ini', delete=False)
26
- self.temp_file.write(self.test_content)
27
- self.temp_file.close()
28
-
29
- def tearDown(self):
30
- """Clean up after each test method."""
31
- if os.path.exists(self.temp_file.name):
32
- os.unlink(self.temp_file.name)
33
-
34
- def test_load_file(self):
35
- """Test loading an INI file."""
36
- result = QuickIni.load_file(self.temp_file.name)
37
- self.assertTrue(result)
38
-
39
- def test_get_value_string(self):
40
- """Test getting string values."""
41
- QuickIni.load_file(self.temp_file.name)
42
- value = QuickIni.get_value("name")
43
- self.assertEqual(value, "TestApp")
44
-
45
- def test_get_value_boolean(self):
46
- """Test getting boolean values."""
47
- QuickIni.load_file(self.temp_file.name)
48
- value = QuickIni.get_value("debug")
49
- self.assertTrue(value)
50
- self.assertIsInstance(value, bool)
51
-
52
- def test_get_value_integer(self):
53
- """Test getting integer values."""
54
- QuickIni.load_file(self.temp_file.name)
55
- value = QuickIni.get_value("port")
56
- self.assertEqual(value, 8080)
57
- self.assertIsInstance(value, int)
58
-
59
- def test_get_value_float(self):
60
- """Test getting float values."""
61
- QuickIni.load_file(self.temp_file.name)
62
- value = QuickIni.get_value("timeout")
63
- self.assertEqual(value, 30.5)
64
- self.assertIsInstance(value, float)
65
-
66
- def test_get_value_default(self):
67
- """Test getting default values for missing keys."""
68
- QuickIni.load_file(self.temp_file.name)
69
- value = QuickIni.get_value("missing_key", "default")
70
- self.assertEqual(value, "default")
71
-
72
- def test_write_value(self):
73
- """Test writing values to file."""
74
- QuickIni.load_file(self.temp_file.name)
75
- result = QuickIni.write_value("new_key", "new_value", add_if_not_found=True)
76
- self.assertEqual(result, "new_value")
77
-
78
- # Verify it was written
79
- value = QuickIni.get_value("new_key")
80
- self.assertEqual(value, "new_value")
81
-
82
- def test_error_handling(self):
83
- """Test error handling for missing files."""
84
- result = QuickIni.load_file("nonexistent_file.ini")
85
- self.assertFalse(result)
86
- error = QuickIni.get_last_error()
87
- self.assertIn("Could not find file", error)
88
-
89
-
90
- if __name__ == '__main__':
91
- unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes