quick-ini 2.1__tar.gz → 3.0__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.1
2
2
  Name: quick_ini
3
- Version: 2.1
3
+ Version: 3.0
4
4
  Summary: A library for reading from and writting to .ini formatted files
5
5
  Home-page: https://github.com/olliez-mods/Quick-Ini
6
6
  Author: OllieZ-Mods
@@ -0,0 +1 @@
1
+ from .quick_ini import QuickIni
@@ -0,0 +1,229 @@
1
+ from urllib.parse import urlparse
2
+ from typing import Type, Any
3
+ import os
4
+
5
+ class QuickIni:
6
+ parsed_ini:dict[str,str|int|bool|float|None] = {}
7
+ loaded_file_path:str=None
8
+ auto_type_convert_g:bool=True
9
+ error_message:str = ""
10
+ web_file:str = None
11
+
12
+ @staticmethod
13
+ def grab_from_url(url:str) -> bool:
14
+ import urllib.request, urllib.error
15
+ try:
16
+ with urllib.request.urlopen(url) as response:
17
+ if(response.status == 200):
18
+ QuickIni.web_file = response.read().decode('utf-8')
19
+ return True
20
+ else:
21
+ QuickIni.error_message = f"Failed to download file from {url}, got response: {response.status}"
22
+ return False
23
+ except urllib.error.HTTPError as e:
24
+ QuickIni.error_message = f"Could not get file from URL: '{e}'"
25
+ return False
26
+
27
+ @staticmethod
28
+ def is_string_url(stri:str) -> bool:
29
+ try:
30
+ res = urlparse(stri)
31
+ return all([res.scheme, res.netloc])
32
+ except ValueError:
33
+ return False
34
+
35
+ @staticmethod
36
+ def get_last_error() -> str:
37
+ """
38
+ Returns the last error thrown by the library
39
+ Returns:
40
+ str: A formatted error message
41
+ """
42
+ return QuickIni.error_message
43
+
44
+ @staticmethod
45
+ def string_to_type(s:str) -> Any:
46
+ """
47
+ Convert a string to the appropriate type (int, float, bool, or str).
48
+
49
+ Parameters:
50
+ s (str): The input string to convert.
51
+
52
+ Returns:
53
+ Any: The converted value as int, float, bool, or str, based on the content of `s`.
54
+ - Returns an int if `s` contains only digits.
55
+ - Returns a float if `s` is a numeric value with a single decimal point.
56
+ - Returns a boolean (True or False) if `s` matches the strings "true" or "false" (case-insensitive).
57
+ - Returns `s` unchanged if no other conditions are met.
58
+ """
59
+ if(s.isnumeric()): return int(s)
60
+ elif(s.count(".") == 1 and s.replace(".", "").isnumeric()): return float(s)
61
+ elif(s.lower() == "false"): return False
62
+ elif(s.lower() == "true"): return True
63
+ return s
64
+
65
+ @staticmethod
66
+ def load_file(file_location:str, auto_type_convert:bool = True, empty_default:Any = None) -> bool:
67
+ """
68
+ Load a ini style file from the file system or from a URL.
69
+ \nUse get_value() or get_all() to access.
70
+ Args:
71
+ name (str): The path/URL of the file to load.
72
+ auto_type_convert (bool): Automaticly convert into recognized types such as True/False, int, float.
73
+ empty_default (Any): The default value when a value is left blank (overrides auto_type_convert).
74
+ Returns:
75
+ bool: True if successfull, False otherwise
76
+ Example:
77
+ >>> load_file("../config.ini", empty_default="")
78
+ True
79
+ """
80
+ QuickIni.parsed_ini = {}
81
+ QuickIni.auto_type_convert_g = auto_type_convert
82
+
83
+ f_data:str = None
84
+
85
+ # Check if the provided location is a URL
86
+ if(QuickIni.is_string_url(file_location)):
87
+ if(not QuickIni.grab_from_url(file_location)):
88
+ # The grab_from_url sets the error message
89
+ return False
90
+ f_data = QuickIni.web_file
91
+ # Otherwise assume it's a file path
92
+ else:
93
+ try:
94
+ f_data = open(str(file_location), 'r', encoding='utf-8').read()
95
+ except FileNotFoundError:
96
+ QuickIni.error_message = f"Could not find file at: '{file_location}'"
97
+ return False
98
+ except IOError as e:
99
+ QuickIni.error_message = f"Error reading file: '{e}'"
100
+ return False
101
+
102
+ lines = f_data.splitlines()
103
+
104
+ for line in lines:
105
+ if(line.startswith("#") or "=" not in line): continue # Ignore comments and lines without "="
106
+ left, right = line.split("=", 1)
107
+ if(not right): right = empty_default
108
+ elif(not auto_type_convert): pass # If we aren't auto converting, skip the conversion
109
+ else: right = QuickIni.string_to_type(right)
110
+
111
+ QuickIni.parsed_ini[left] = right
112
+
113
+ QuickIni.loaded_file_path=file_location
114
+ return True
115
+
116
+ @staticmethod
117
+ def get_value(name:str, default:Any = None, expected_type:Type = None) -> Any:
118
+ """
119
+ Returns the value attached to the given name.
120
+ \nIf no setting is found, it will return the passed in default
121
+ Args:
122
+ name (str): The name of the setting
123
+ default (Any): What will be returned if nothing is found
124
+ expected_type (Type): Will make sure the returned object is of this Type, otherwise Raise()
125
+ Return:
126
+ Any: The value associated with the name
127
+ Example:
128
+ >>> get_value("do_debug_logs", False)
129
+ True
130
+ """
131
+ value:Any = QuickIni.parsed_ini.get(name, default)
132
+ if(expected_type != None):
133
+ if(not isinstance(value, expected_type)):
134
+ QuickIni.error_message = f"Expected type '{expected_type.__name__}', instead got '{type(value).__name__}'"
135
+ raise(ValueError(QuickIni.error_message))
136
+ return value
137
+
138
+ def write_value(key:str, value:Any, add_if_not_found:bool=False, update_locally:bool=True, do_backup:bool=False) -> Any:
139
+ """
140
+ Writes a key-value pair to the loaded .ini file, with an optional dry run mode.
141
+
142
+ Parameters:
143
+ key (str): The key to update or add in the .ini file.
144
+ value (Any): The value to write, which will be converted to a string.
145
+ add_if_not_found (bool): If True, adds the key-value pair if the key is not found in the file (default is False).
146
+ update_localy (bool): If True, updates the parsed_ini dictionary locally with the new key-value pair (default is True).
147
+ do_backup (bool): If True, creates a backup of the original file before making changes, and removes if successful.
148
+
149
+ Returns:
150
+ Any: The value written or simulated, with optional type conversion if `auto_type_convert` was enabled.
151
+
152
+ Raises:
153
+ ValueError: If no file path is loaded or if the value cannot be converted to a string.
154
+ FileNotFoundError: If the specified file cannot be found.
155
+ PermissionError: If there is an issue with file permissions.
156
+ IOError: If there is a read or write error.
157
+
158
+ Example:
159
+ >>> write_value("example_key", 42, add_if_not_found=True)
160
+ 42
161
+ """
162
+
163
+ if(not QuickIni.loaded_file_path):
164
+ QuickIni.error_message = "No loaded file path, was a local file loaded?"
165
+ raise(ValueError(QuickIni.error_message))
166
+
167
+ try: str(value)
168
+ except(TypeError, ValueError):
169
+ QuickIni.error_message = "Could not convert value to a string"
170
+ raise(ValueError(QuickIni.error_message))
171
+
172
+ key_value_pair = f"{str(key)}={str(value)}\n"
173
+
174
+ try:
175
+ file = open(str(QuickIni.loaded_file_path), 'r', encoding='utf-8')
176
+ lines = file.readlines()
177
+ file.close()
178
+
179
+ # Create backup
180
+ if(do_backup):
181
+ backup_file = open(f"{str(QuickIni.loaded_file_path)}.backup", 'w', encoding='utf-8')
182
+ backup_file.writelines(lines)
183
+ backup_file.close()
184
+ print("Backup created at", f"{str(QuickIni.loaded_file_path)}.backup")
185
+
186
+ key_found=False
187
+ last_line=""
188
+ file = open(str(QuickIni.loaded_file_path), 'w')
189
+ for line in lines:
190
+ if(not line.startswith("#") and not key_found and line.startswith(f"{key}=")):
191
+ last_line = key_value_pair
192
+ key_found = True
193
+ else:
194
+ last_line = line
195
+ file.write(last_line)
196
+
197
+ if(not key_found):
198
+ if(add_if_not_found):
199
+ if(not last_line.endswith("\n")):
200
+ file.write("\n")
201
+ file.write(f"{key_value_pair}")
202
+ else:
203
+ QuickIni.error_message = f"Could not find key '{key}'"
204
+ raise(ValueError(QuickIni.error_message))
205
+
206
+ file.close()
207
+
208
+ except FileNotFoundError as e:
209
+ QuickIni.error_message = f"Could not find file '{QuickIni.loaded_file_path}'"
210
+ raise(e)
211
+ except PermissionError as e:
212
+ QuickIni.error_message = f"Permission error for file '{QuickIni.loaded_file_path}'"
213
+ raise(e)
214
+ except IOError as e:
215
+ QuickIni.error_message = f"IOError when reading or writting to the file '{QuickIni.loaded_file_path}'"
216
+ raise(e)
217
+
218
+ v = QuickIni.string_to_type(str(value)) if QuickIni.auto_type_convert_g else str(value)
219
+ if(update_locally):
220
+ QuickIni.parsed_ini[str(key)] = v
221
+
222
+ # Remove backup
223
+ if(do_backup):
224
+ try: os.remove(f"{str(QuickIni.loaded_file_path)}.backup")
225
+ except Exception as e:
226
+ QuickIni.error_message = "Failed to remove backup file"
227
+ raise(e)
228
+
229
+ return v
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quick_ini
3
- Version: 2.1
3
+ Version: 3.0
4
4
  Summary: A library for reading from and writting to .ini formatted files
5
5
  Home-page: https://github.com/olliez-mods/Quick-Ini
6
6
  Author: OllieZ-Mods
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="quick_ini",
5
- version="2.1",
5
+ version="3.0",
6
6
  description="A library for reading from and writting to .ini formatted files",
7
7
  long_description=open('README.md').read(),
8
8
  long_description_content_type="text/markdown",
@@ -1 +0,0 @@
1
- from .quick_ini import get_last_error, load_file, get_value, write_value
@@ -1,225 +0,0 @@
1
- from urllib.parse import urlparse
2
- from typing import Type, Any
3
- import os
4
-
5
- parsed_ini:dict[str,str|int|bool|float|None] = {}
6
- loaded_file_path:str=None
7
- auto_type_convert_g:bool=True
8
- error_message:str = ""
9
- web_file:str = None
10
-
11
- def grab_from_url(url:str) -> bool:
12
- global web_file, error_message
13
- import urllib.request, urllib.error
14
- try:
15
- with urllib.request.urlopen(url) as response:
16
- if(response.status == 200):
17
- web_file = response.read().decode('utf-8')
18
- return True
19
- else:
20
- error_message = f"Failed to download file from {url}, got response: {response.status}"
21
- return False
22
- except urllib.error.HTTPError as e:
23
- error_message = f"Could not get file from URL: '{e}'"
24
- return False
25
-
26
- def is_string_url(stri:str) -> bool:
27
- try:
28
- res = urlparse(stri)
29
- return all([res.scheme, res.netloc])
30
- except ValueError:
31
- return False
32
-
33
- def get_last_error() -> str:
34
- """
35
- Returns the last error thrown by the library
36
- Returns:
37
- str: A formatted error message
38
- """
39
- return error_message
40
-
41
- def string_to_type(s:str) -> Any:
42
- """
43
- Convert a string to the appropriate type (int, float, bool, or str).
44
-
45
- Parameters:
46
- s (str): The input string to convert.
47
-
48
- Returns:
49
- Any: The converted value as int, float, bool, or str, based on the content of `s`.
50
- - Returns an int if `s` contains only digits.
51
- - Returns a float if `s` is a numeric value with a single decimal point.
52
- - Returns a boolean (True or False) if `s` matches the strings "true" or "false" (case-insensitive).
53
- - Returns `s` unchanged if no other conditions are met.
54
- """
55
- if(s.isnumeric()): return int(s)
56
- elif(s.count(".") == 1 and s.replace(".", "").isnumeric()): return float(s)
57
- elif(s.lower() == "false"): return False
58
- elif(s.lower() == "true"): return True
59
- return s
60
-
61
- def load_file(file_location:str, auto_type_convert:bool = True, empty_default:Any = None) -> bool:
62
- """
63
- Load a ini style file from the file system or from a URL.
64
- \nUse get_value() or get_all() to access.
65
- Args:
66
- name (str): The path/URL of the file to load.
67
- auto_type_convert (bool): Automaticly convert into recognized types such as True/False, int, float.
68
- empty_default (Any): The default value when a value is left blank (overrides auto_type_convert).
69
- Returns:
70
- bool: True if successfull, False otherwise
71
- Example:
72
- >>> load_file("../config.ini", empty_default="")
73
- True
74
- """
75
- global error_message, parsed_ini,loaded_file_path,auto_type_convert_g
76
- parsed_ini = {}
77
- auto_type_convert_g = auto_type_convert
78
-
79
- f_data:str = None
80
-
81
- # Check if the provided location is a URL
82
- if(is_string_url(file_location)):
83
- if(not grab_from_url(file_location)):
84
- # The grab_from_url sets the error message
85
- return False
86
- f_data = web_file
87
- # Otherwise assume it's a file path
88
- else:
89
- try:
90
- f_data = open(str(file_location), 'r', encoding='utf-8').read()
91
- except FileNotFoundError:
92
- error_message = f"Could not find file at: '{file_location}'"
93
- return False
94
- except IOError as e:
95
- error_message = f"Error reading file: '{e}'"
96
- return False
97
-
98
- lines = f_data.splitlines()
99
-
100
- for line in lines:
101
- if(line.startswith("#") or "=" not in line): continue # Ignore comments and lines without "="
102
- left, right = line.split("=", 1)
103
- if(not right): right = empty_default
104
- elif(not auto_type_convert): pass # If we aren't auto converting, skip the conversion
105
- else: right = string_to_type(right)
106
-
107
- parsed_ini[left] = right
108
-
109
- loaded_file_path=file_location
110
- return True
111
-
112
- def get_value(name:str, default:Any = None, expected_type:Type = None) -> Any:
113
- """
114
- Returns the value attached to the given name.
115
- \nIf no setting is found, it will return the passed in default
116
- Args:
117
- name (str): The name of the setting
118
- default (Any): What will be returned if nothing is found
119
- expected_type (Type): Will make sure the returned object is of this Type, otherwise Raise()
120
- Return:
121
- Any: The value associated with the name
122
- Example:
123
- >>> get_value("do_debug_logs", False)
124
- True
125
- """
126
- global error_message
127
- value:Any = parsed_ini.get(name, default)
128
- if(expected_type != None):
129
- if(not isinstance(value, expected_type)):
130
- error_message = f"Expected type '{expected_type.__name__}', instead got '{type(value).__name__}'"
131
- raise(ValueError(error_message))
132
- return value
133
-
134
- def write_value(key:str, value:Any, add_if_not_found:bool=False, update_locally:bool=True, do_backup:bool=False) -> Any:
135
- """
136
- Writes a key-value pair to the loaded .ini file, with an optional dry run mode.
137
-
138
- Parameters:
139
- key (str): The key to update or add in the .ini file.
140
- value (Any): The value to write, which will be converted to a string.
141
- add_if_not_found (bool): If True, adds the key-value pair if the key is not found in the file (default is False).
142
- update_localy (bool): If True, updates the parsed_ini dictionary locally with the new key-value pair (default is True).
143
- do_backup (bool): If True, creates a backup of the original file before making changes.
144
-
145
- Returns:
146
- Any: The value written or simulated, with optional type conversion if `auto_type_convert` was enabled.
147
-
148
- Raises:
149
- ValueError: If no file path is loaded or if the value cannot be converted to a string.
150
- FileNotFoundError: If the specified file cannot be found.
151
- PermissionError: If there is an issue with file permissions.
152
- IOError: If there is a read or write error.
153
-
154
- Example:
155
- >>> write_value("example_key", 42, add_if_not_found=True)
156
- 42
157
- """
158
- global loaded_file_path, parsed_ini, auto_type_convert_g, error_message
159
-
160
- if(not loaded_file_path):
161
- error_message = "No loaded file path, was a local file loaded?"
162
- raise(ValueError(error_message))
163
-
164
- try: str(value)
165
- except(TypeError, ValueError):
166
- error_message = "Could not convert value to a string"
167
- raise(ValueError(error_message))
168
-
169
- key_value_pair = f"{str(key)}={str(value)}\n"
170
-
171
- try:
172
- file = open(str(loaded_file_path), 'r', encoding='utf-8')
173
- lines = file.readlines()
174
- file.close()
175
-
176
- # Create backup
177
- if(do_backup):
178
- backup_file = open(f"{str(loaded_file_path)}.backup", 'w', encoding='utf-8')
179
- backup_file.writelines(lines)
180
- backup_file.close()
181
-
182
- key_found=False
183
- last_line=""
184
- file = open(str(loaded_file_path), 'w')
185
- for line in lines:
186
- if(not line.startswith("#") and not key_found and line.startswith(f"{key}=")):
187
- last_line = key_value_pair
188
- key_found = True
189
- else:
190
- last_line = line
191
- file.write(last_line)
192
-
193
- if(not key_found):
194
- if(add_if_not_found):
195
- if(not last_line.endswith("\n")):
196
- file.write("\n")
197
- file.write(f"{key_value_pair}")
198
- else:
199
- error_message = f"Could not find key '{key}'"
200
- raise(ValueError(error_message))
201
-
202
- file.close()
203
-
204
- except FileNotFoundError as e:
205
- error_message = f"Could not find file '{loaded_file_path}'"
206
- raise(e)
207
- except PermissionError as e:
208
- error_message = f"Permission error for file '{loaded_file_path}'"
209
- raise(e)
210
- except IOError as e:
211
- error_message = f"IOError when reading or writting to the file '{loaded_file_path}'"
212
- raise(e)
213
-
214
- v = string_to_type(str(value)) if auto_type_convert_g else str(value)
215
- if(update_locally):
216
- parsed_ini[str(key)] = v
217
-
218
- # Remove backup
219
- if(do_backup):
220
- try: os.remove(f"{str(loaded_file_path)}.backup")
221
- except Exception as e:
222
- error_message = "Failed to remove backup file"
223
- raise(e)
224
-
225
- return v
File without changes
File without changes
File without changes