medicafe 0.251027.2__py3-none-any.whl → 0.251029.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.

Potentially problematic release.


This version of medicafe might be problematic. Click here for more details.

@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Config Editor Helper Functions
5
+ Provides JSON loading, validation, backup, and atomic write utilities for the config editor.
6
+ Compatible with Python 3.4.4 and Windows XP.
7
+ """
8
+
9
+ import os
10
+ import json
11
+ import time
12
+ import tempfile
13
+ import shutil
14
+ import platform
15
+ from collections import OrderedDict
16
+
17
+ # Sensitive keys that should trigger extra confirmation
18
+ SENSITIVE_KEYS = ['password', 'token', 'secret', 'key', 'auth', 'credential', 'api_key', 'client_secret']
19
+
20
+ def resolve_config_path(default_path):
21
+ """
22
+ Resolve the config file path using the same logic as MediLink_ConfigLoader.
23
+ This ensures the editor finds the config file in the same location as the main application.
24
+
25
+ Args:
26
+ default_path: The default path to try first
27
+
28
+ Returns:
29
+ str: The resolved config file path
30
+ """
31
+ # If the default path exists, use it
32
+ if os.path.exists(default_path):
33
+ return default_path
34
+
35
+ # Try platform-specific fallbacks (same logic as MediLink_ConfigLoader)
36
+ if platform.system() == 'Windows' and platform.release() == 'XP':
37
+ # Use F: paths for Windows XP
38
+ xp_path = "F:\\Medibot\\json\\config.json"
39
+ if os.path.exists(xp_path):
40
+ return xp_path
41
+ elif platform.system() == 'Windows':
42
+ # Use current working directory for other versions of Windows
43
+ cwd_path = os.path.join(os.getcwd(), 'json', 'config.json')
44
+ if os.path.exists(cwd_path):
45
+ return cwd_path
46
+
47
+ # If no fallback paths exist, return the original default path
48
+ # (the editor will handle the missing file gracefully)
49
+ return default_path
50
+
51
+ def load_config_safe(config_path):
52
+ """
53
+ Safely load config.json with fallback to empty structure.
54
+ Returns (config_dict, error_message)
55
+ """
56
+ try:
57
+ if not os.path.exists(config_path):
58
+ return {}, "Config file not found: {}".format(config_path)
59
+
60
+ with open(config_path, 'r', encoding='utf-8') as f:
61
+ config = json.load(f, object_pairs_hook=OrderedDict)
62
+
63
+ if not isinstance(config, dict):
64
+ return {}, "Config file does not contain a valid JSON object"
65
+
66
+ return config, None
67
+
68
+ except json.JSONDecodeError as e:
69
+ return {}, "Invalid JSON in config file: {}".format(str(e))
70
+ except Exception as e:
71
+ return {}, "Error loading config file: {}".format(str(e))
72
+
73
+ def create_backup(config_path):
74
+ """
75
+ Create a timestamped backup of the config file.
76
+ Returns backup_path or None if failed.
77
+ """
78
+ try:
79
+ if not os.path.exists(config_path):
80
+ return None
81
+
82
+ # Create timestamp in format YYYYMMDD_HHMMSS
83
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
84
+ backup_path = "{}.bak.{}".format(config_path, timestamp)
85
+
86
+ shutil.copy2(config_path, backup_path)
87
+ return backup_path
88
+
89
+ except Exception as e:
90
+ print("Warning: Could not create backup: {}".format(str(e)))
91
+ return None
92
+
93
+ def save_config_atomic(config_path, config_dict):
94
+ """
95
+ Save config dictionary to file using atomic write (temp file + rename).
96
+ Compatible with Windows XP.
97
+ Returns (success, error_message)
98
+ """
99
+ try:
100
+ # Create temp file in same directory as target
101
+ temp_dir = os.path.dirname(config_path)
102
+ temp_fd, temp_path = tempfile.mkstemp(suffix='.tmp', dir=temp_dir)
103
+
104
+ try:
105
+ # Write to temp file
106
+ with os.fdopen(temp_fd, 'w', encoding='utf-8') as f:
107
+ json.dump(config_dict, f, ensure_ascii=False, indent=4, separators=(',', ': '))
108
+
109
+ # Atomic rename (works on Windows XP)
110
+ if os.name == 'nt': # Windows
111
+ if os.path.exists(config_path):
112
+ os.remove(config_path)
113
+ os.rename(temp_path, config_path)
114
+ else: # Unix-like
115
+ os.rename(temp_path, config_path)
116
+
117
+ return True, None
118
+
119
+ except Exception as e:
120
+ # Clean up temp file on error
121
+ try:
122
+ os.close(temp_fd)
123
+ if os.path.exists(temp_path):
124
+ os.remove(temp_path)
125
+ except:
126
+ pass
127
+ raise e
128
+
129
+ except Exception as e:
130
+ return False, "Error saving config: {}".format(str(e))
131
+
132
+ def validate_json_structure(data):
133
+ """
134
+ Validate that data can be serialized to valid JSON.
135
+ Returns (is_valid, error_message)
136
+ """
137
+ try:
138
+ json.dumps(data, ensure_ascii=False)
139
+ return True, None
140
+ except Exception as e:
141
+ return False, "Invalid JSON structure: {}".format(str(e))
142
+
143
+ def is_sensitive_key(key_name):
144
+ """
145
+ Check if a key name appears to be sensitive.
146
+ """
147
+ key_lower = key_name.lower()
148
+ return any(sensitive in key_lower for sensitive in SENSITIVE_KEYS)
149
+
150
+ def get_value_type(value):
151
+ """
152
+ Determine the type of a JSON value for display purposes.
153
+ """
154
+ if isinstance(value, str):
155
+ return "string"
156
+ elif isinstance(value, (int, float)):
157
+ return "number"
158
+ elif isinstance(value, bool):
159
+ return "boolean"
160
+ elif isinstance(value, list):
161
+ return "array"
162
+ elif isinstance(value, dict):
163
+ return "object"
164
+ else:
165
+ return "unknown"
166
+
167
+ def format_value_for_display(value, max_length=50):
168
+ """
169
+ Format a value for display in the editor, truncating if too long.
170
+ """
171
+ if isinstance(value, str):
172
+ if len(value) > max_length:
173
+ return '"{}..."'.format(value[:max_length-3])
174
+ else:
175
+ return '"{}"'.format(value)
176
+ elif isinstance(value, (int, float)):
177
+ return str(value)
178
+ elif isinstance(value, bool):
179
+ return "true" if value else "false"
180
+ elif isinstance(value, list):
181
+ return "[{} items]".format(len(value))
182
+ elif isinstance(value, dict):
183
+ return "{{{} keys}}".format(len(value))
184
+ else:
185
+ return str(value)
186
+
187
+ def clear_config_cache():
188
+ """
189
+ Clear the MediLink_ConfigLoader cache after config changes.
190
+ """
191
+ try:
192
+ # Import here to avoid issues if MediCafe not available
193
+ import sys
194
+ import os
195
+
196
+ # Add parent directory to path to find MediCafe
197
+ parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
198
+ if parent_dir not in sys.path:
199
+ sys.path.insert(0, parent_dir)
200
+
201
+ from MediCafe.MediLink_ConfigLoader import clear_config_cache as clear_loader_cache
202
+
203
+ # Use the loader's own cache clearing function
204
+ clear_loader_cache()
205
+
206
+ return True, None
207
+
208
+ except Exception as e:
209
+ return False, "Warning: Could not clear config cache: {}".format(str(e))
210
+
211
+ def get_nested_value(config, path_list):
212
+ """
213
+ Get a value from nested dictionary using path list.
214
+ Returns (value, found) where found is boolean.
215
+ """
216
+ current = config
217
+ try:
218
+ for key in path_list:
219
+ current = current[key]
220
+ return current, True
221
+ except (KeyError, TypeError):
222
+ return None, False
223
+
224
+ def set_nested_value(config, path_list, value):
225
+ """
226
+ Set a value in nested dictionary using path list.
227
+ Creates intermediate dictionaries if needed.
228
+ """
229
+ current = config
230
+ for key in path_list[:-1]:
231
+ if key not in current or not isinstance(current[key], dict):
232
+ current[key] = OrderedDict()
233
+ current = current[key]
234
+ current[path_list[-1]] = value
235
+
236
+ def get_path_string(path_list):
237
+ """
238
+ Convert path list to breadcrumb string.
239
+ """
240
+ if not path_list:
241
+ return "config"
242
+ return "config > " + " > ".join(path_list)
243
+
244
+ def validate_key_name(key_name, existing_keys):
245
+ """
246
+ Validate a new key name.
247
+ Returns (is_valid, error_message)
248
+ """
249
+ if not key_name:
250
+ return False, "Key name cannot be empty"
251
+
252
+ if key_name in existing_keys:
253
+ return False, "Key '{}' already exists".format(key_name)
254
+
255
+ # Check for invalid characters that might break JSON
256
+ invalid_chars = ['"', '\\', '/', '\n', '\r', '\t']
257
+ for char in invalid_chars:
258
+ if char in key_name:
259
+ return False, "Key name contains invalid character: '{}'".format(char)
260
+
261
+ return True, None
262
+
263
+ def parse_value_input(value_str, value_type):
264
+ """
265
+ Parse user input string into appropriate Python type.
266
+ Returns (parsed_value, error_message)
267
+ """
268
+ try:
269
+ if value_type == "string":
270
+ # Remove surrounding quotes if present
271
+ if value_str.startswith('"') and value_str.endswith('"'):
272
+ value_str = value_str[1:-1]
273
+ return value_str, None
274
+
275
+ elif value_type == "number":
276
+ # Try int first, then float
277
+ if '.' in value_str:
278
+ return float(value_str), None
279
+ else:
280
+ return int(value_str), None
281
+
282
+ elif value_type == "boolean":
283
+ value_lower = value_str.lower()
284
+ if value_lower in ['true', 't', 'yes', 'y', '1']:
285
+ return True, None
286
+ elif value_lower in ['false', 'f', 'no', 'n', '0']:
287
+ return False, None
288
+ else:
289
+ return None, "Invalid boolean value. Use true/false, yes/no, or 1/0"
290
+
291
+ elif value_type == "array":
292
+ # Simple array parsing - comma separated values
293
+ if not value_str.strip():
294
+ return [], None
295
+
296
+ # Split by comma and strip whitespace
297
+ items = [item.strip() for item in value_str.split(',')]
298
+ return items, None
299
+
300
+ elif value_type == "object":
301
+ # For now, return empty object - user can add keys later
302
+ return OrderedDict(), None
303
+
304
+ else:
305
+ return None, "Unknown value type: {}".format(value_type)
306
+
307
+ except ValueError as e:
308
+ return None, "Invalid value format: {}".format(str(e))
309
+ except Exception as e:
310
+ return None, "Error parsing value: {}".format(str(e))
MediCafe/__init__.py CHANGED
@@ -27,7 +27,7 @@ Smart Import System:
27
27
  api_suite = get_api_access()
28
28
  """
29
29
 
30
- __version__ = "0.251027.2"
30
+ __version__ = "0.251029.0"
31
31
  __author__ = "Daniel Vidaud"
32
32
  __email__ = "daniel@personalizedtransformation.com"
33
33