diff --git a/LiteJsonDb/LiteJsonDb.py b/LiteJsonDb/LiteJsonDb.py index aaca364..eef8238 100644 --- a/LiteJsonDb/LiteJsonDb.py +++ b/LiteJsonDb/LiteJsonDb.py @@ -1,5 +1,6 @@ import os import logging +import sys from typing import Any, Dict, Optional from .handler import ( Encryption, DatabaseOperations, DataManipulation @@ -24,6 +25,12 @@ def setup_logging(enable_log): if enable_log: logging.basicConfig(filename=os.path.join(DATABASE_DIR, 'LiteJsonDb.log'), level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + # Add console handler for user messages + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(logging.INFO) + console_formatter = logging.Formatter('%(message)s') + console_handler.setFormatter(console_formatter) + logging.getLogger().addHandler(console_handler) class JsonDB(Encryption, DatabaseOperations, DataManipulation): """ @@ -57,7 +64,8 @@ def __init__(self, filename="db.json", backup_filename="db_backup.json", self.observers = {} self.csv_exporter = CSVExporter(DATABASE_DIR) setup_logging(self.enable_log) - Encryption.__init__(self, encryption_method, encryption_key) + self.logger = logging.getLogger('LiteJsonDb') + Encryption.__init__(self, encryption_method, encryption_key) DatabaseOperations.__init__(self, enable_log, auto_backup) DataManipulation.__init__(self) self._load_db() @@ -75,7 +83,7 @@ def backup_to_telegram(self, token: str, chat_id: str): try: telegram_bot.backup_to_telegram(self.filename) except Exception as e: - print(f"\033[90m#bugs\033[0m Telegram backup took a wrong turn! Error: {e}") + self.logger.error(f"\033[90m#bugs\033[0m Telegram backup took a wrong turn! Error: {e}") if self.enable_log: logging.error(f"Error sending backup to Telegram: {e}") @@ -91,40 +99,42 @@ def export_to_csv(self, data_key: Optional[str] = None): data = self.db[data_key] csv_path = self.csv_exporter.export(data, f"{data_key}_export.csv") if csv_path: - print(f"🎉 Hooray! CSV exported to: {csv_path}") + self.logger.info(f"🎉 Hooray! CSV exported to: {csv_path}") else: - print(f"\033[90m#bugs\033[0m Could not export '{data_key}' to CSV!") + self.logger.error(f"\033[90m#bugs\033[0m Could not export '{data_key}' to CSV!") else: - print(f"\033[90m#bugs\033[0m Key '{data_key}' not found, is it hiding? Tip: Double-check it!") + self.logger.error(f"\033[90m#bugs\033[0m Key '{data_key}' not found, is it hiding? Tip: Double-check it!") else: if self.db: csv_path = self.csv_exporter.export(self.db, "full_database_export.csv") if csv_path: - print(f"🎉 Full database exported to: {csv_path}") + self.logger.info(f"🎉 Full database exported to: {csv_path}") else: - print("\033[90m#bugs\033[0m Database export failed. It's shy!") + self.logger.error("\033[90m#bugs\033[0m Database export failed. It's shy!") else: - print("\033[90m#bugs\033[0m Database is empty, ghost town vibes!") + self.logger.error("\033[90m#bugs\033[0m Database is empty, ghost town vibes!") - def search_data(self, value: Any, key: Optional[str] = None) -> Optional[Dict[str, Any]]: + def search_data(self, value: Any, key: Optional[str] = None, substring: bool = False, case_sensitive: bool = True) -> Optional[Dict[str, Any]]: """ Searches for a value within the database. - Args: - value (Any): The value to search for. - key (Optional[str]): If provided, searches only within the values associated with this key. - Returns: - Optional[Dict[str, Any]]: Returns the matching dictionary or None if not found. + Args: + value (Any): The value to search for. + key (Optional[str]): If provided, searches only within the values associated with this key. + substring (bool): If True, perform substring search. Defaults to False. + case_sensitive (bool): If False, perform case-insensitive search. Defaults to True. + Returns: + Optional[Dict[str, Any]]: Returns the matching dictionary or None if not found. """ try: - result = search_data(self.db, value, key) + result = search_data(self.db, value, key, substring=substring, case_sensitive=case_sensitive) if result: return result else: - print("\033[90m#info\033[0m Not found! Try another quest?") + self.logger.info("Not found! Try another quest?") return None except Exception as e: - print(f"\033[90m#bugs\033[0m Search party got lost! Error: {e}") + self.logger.error(f"Search party got lost! Error: {e}") return None @staticmethod diff --git a/LiteJsonDb/__pycache__/LiteJsonDb.cpython-311.pyc b/LiteJsonDb/__pycache__/LiteJsonDb.cpython-311.pyc new file mode 100644 index 0000000..d0b41a6 Binary files /dev/null and b/LiteJsonDb/__pycache__/LiteJsonDb.cpython-311.pyc differ diff --git a/LiteJsonDb/__pycache__/__init__.cpython-311.pyc b/LiteJsonDb/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..5cd8c1d Binary files /dev/null and b/LiteJsonDb/__pycache__/__init__.cpython-311.pyc differ diff --git a/LiteJsonDb/handler/__pycache__/__init__.cpython-311.pyc b/LiteJsonDb/handler/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1435b73 Binary files /dev/null and b/LiteJsonDb/handler/__pycache__/__init__.cpython-311.pyc differ diff --git a/LiteJsonDb/handler/__pycache__/db_operations.cpython-311.pyc b/LiteJsonDb/handler/__pycache__/db_operations.cpython-311.pyc new file mode 100644 index 0000000..c6f1ef3 Binary files /dev/null and b/LiteJsonDb/handler/__pycache__/db_operations.cpython-311.pyc differ diff --git a/LiteJsonDb/handler/__pycache__/encrypt.cpython-311.pyc b/LiteJsonDb/handler/__pycache__/encrypt.cpython-311.pyc new file mode 100644 index 0000000..6ce7795 Binary files /dev/null and b/LiteJsonDb/handler/__pycache__/encrypt.cpython-311.pyc differ diff --git a/LiteJsonDb/handler/__pycache__/method.cpython-311.pyc b/LiteJsonDb/handler/__pycache__/method.cpython-311.pyc new file mode 100644 index 0000000..b8c66df Binary files /dev/null and b/LiteJsonDb/handler/__pycache__/method.cpython-311.pyc differ diff --git a/LiteJsonDb/handler/db_operations.py b/LiteJsonDb/handler/db_operations.py index 5d4878a..d2548ff 100644 --- a/LiteJsonDb/handler/db_operations.py +++ b/LiteJsonDb/handler/db_operations.py @@ -33,7 +33,7 @@ def _load_db(self) -> None: if self.enable_log: logging.info(f"Database file created: {self.filename}") except OSError as e: - print(f"\033[91m#bugs\033[0m Unable to create database file: {e}") + self.logger.error(f"\033[91m#bugs\033[0m Unable to create database file: {e}") raise try: with open(self.filename, 'r') as file: @@ -45,7 +45,7 @@ def _load_db(self) -> None: if self.enable_log: logging.info(f"Database loaded from: {self.filename}") except (OSError, json.JSONDecodeError) as e: - print(f"\033[91m#bugs\033[0m Unable to load database file: {e}") + self.logger.error(f"\033[91m#bugs\033[0m Unable to load database file: {e}") raise def _save_db(self) -> None: @@ -59,7 +59,7 @@ def _save_db(self) -> None: if self.enable_log: logging.info(f"Database saved to {self.filename}") except OSError as e: - print(f"\033[91m#bugs\033[0m Could not save database: {e}") + self.logger.error(f"\033[91m#bugs\033[0m Could not save database: {e}") raise def _backup_db(self) -> None: @@ -72,7 +72,7 @@ def _backup_db(self) -> None: if self.enable_log: logging.info(f"Backup created: {self.backup_filename}") except OSError as e: - print(f"\033[91m#bugs\033[0m Unable to create backup: {e}") + self.logger.error(f"\033[91m#bugs\033[0m Unable to create backup: {e}") raise def _restore_db(self) -> None: @@ -86,9 +86,9 @@ def _restore_db(self) -> None: if self.enable_log: logging.info(f"Database restored from backup: {self.backup_filename}") except OSError as e: - print(f"\033[91m#bugs\033[0m Unable to restore database: {e}") + self.logger.error(f"\033[91m#bugs\033[0m Unable to restore database: {e}") raise else: - print("\033[91m#bugs\033[0m No backup file found.") + self.logger.error("\033[91m#bugs\033[0m No backup file found.") if self.enable_log: logging.error("No backup file found to restore.") \ No newline at end of file diff --git a/LiteJsonDb/handler/encrypt.py b/LiteJsonDb/handler/encrypt.py index fc221cc..ec36a86 100644 --- a/LiteJsonDb/handler/encrypt.py +++ b/LiteJsonDb/handler/encrypt.py @@ -140,5 +140,5 @@ def _fernet_decrypt(self, encoded_data: str) -> Dict[str, Any]: decoded_data = self.fernet.decrypt(encoded_data.encode('utf-8')) return json.loads(decoded_data) except Exception as e: - print("\033[91m#bugs\033[0m Fernet decryption failed.") + self.logger.error("\033[91m#bugs\033[0m Fernet decryption failed.") raise ValueError("\033[91m#bugs\033[0m Decryption failed: invalid key or data.") \ No newline at end of file diff --git a/LiteJsonDb/handler/method.py b/LiteJsonDb/handler/method.py index 6df5bf9..ba05518 100644 --- a/LiteJsonDb/handler/method.py +++ b/LiteJsonDb/handler/method.py @@ -30,13 +30,14 @@ def validate_data(self, data: Any) -> bool: types = {} for key, value in data.items(): if not isinstance(key, str): - print(f"\033[91m#bugs\033[0m Key '{key}' must be a string. Did we stumble upon a non-string key?") + self.logger.error(f"\033[91m#bugs\033[0m Key '{key}' must be a string. Did we stumble upon a non-string key?") return False if key in types and types[key] != type(value): - print(f"\033[91m#bugs\033[0m Conflicting types for key '{key}'.") + self.logger.error(f"\033[91m#bugs\033[0m Conflicting types for key '{key}'.") return False - types[key] = type(value) return all(isinstance(value, (str, int, float, list, dict, bool, None)) for value in data.values()) + self.logger.error(f"\033[91m#bugs\033[0m Data must be a dictionary.") + return False print(f"\033[91m#bugs\033[0m Data must be a dictionary.") return False @@ -107,7 +108,7 @@ def get_data(self, key: str) -> Optional[Any]: if k in data: data = data[k] else: - print(f"\033[91m#bugs\033[0m No data found at key '{key}'. Double-check the key or try a different path.") + self.logger.error(f"\033[91m#bugs\033[0m No data found at key '{key}'. Double-check the key or try a different path.") return None return data @@ -123,11 +124,11 @@ def set_data(self, key: str, value: Optional[Any] = None) -> None: value = {} if not self.validate_data(value): - print(f"\033[91m#bugs\033[0m Invalid data format. Ensure your data is a dictionary with consistent types.") + self.logger.error(f"\033[91m#bugs\033[0m Invalid data format. Ensure your data is a dictionary with consistent types.") return if self.key_exists(key): - print(f"\033[91m#bugs\033[0m Key '{key}' already exists. Use db.edit_data('{key}', new_value) to update or add new data.") + self.logger.error(f"\033[91m#bugs\033[0m Key '{key}' already exists. Use db.edit_data('{key}', new_value) to update or add new data.") return self._set_child(self.db, key, value) @@ -144,11 +145,11 @@ def edit_data(self, key: str, value: Any) -> None: value (Any): The new value. """ if not self.key_exists(key): - print(f"\033[91m#bugs\033[0m Key '{key}' doesn't exist, cannot edit. Use 'set_data' to add new data.") + self.logger.error(f"\033[91m#bugs\033[0m Key '{key}' doesn't exist, cannot edit. Use 'set_data' to add new data.") return if not self.validate_data(value): - print(f"\033[91m#bugs\033[0m Invalid data format. Ensure your data is a dictionary with consistent types.") + self.logger.error(f"\033[91m#bugs\033[0m Invalid data format. Ensure your data is a dictionary with consistent types.") return keys = key.split('/') @@ -165,13 +166,13 @@ def edit_data(self, key: str, value: Any) -> None: if isinstance(increment_value, (int, float)): current_data[field] += increment_value else: - print(f"\033[91m#bugs\033[0m Increment value for '{field}' is not a number. Provide a numeric value for incrementing (e.g., db.edit_data('users/1', {{'increment': {{'score': 5}}}})).") + self.logger.error(f"\033[91m#bugs\033[0m Increment value for '{field}' is not a number. Provide a numeric value for incrementing (e.g., db.edit_data('users/1', {{'increment': {{'score': 5}}}})).") return else: - print(f"\033[91m#bugs\033[0m Field '{field}' is not a number. Ensure the field exists and is a number before incrementing.") + self.logger.error(f"\033[91m#bugs\033[0m Field '{field}' is not a number. Ensure the field exists and is a number before incrementing.") return else: - print(f"\033[91m#bugs\033[0m Field '{field}' doesn't exist. Make sure the field exists in the data structure; use db.edit_data to set initial values.") + self.logger.error(f"\033[91m#bugs\033[0m Field '{field}' doesn't exist. Make sure the field exists in the data structure; use db.edit_data to set initial values.") return else: if isinstance(current_data, dict): @@ -238,14 +239,14 @@ def remove_data(self, key: str) -> None: if k in data: data = data[k] else: - print(f"\033[91m#bugs\033[0m Key '{key}' doesn't exist, cannot remove. Make sure the key path is correct.") + self.logger.error(f"\033[91m#bugs\033[0m Key '{key}' doesn't exist, cannot remove. Make sure the key path is correct.") return if keys[-1] in data: del data[keys[-1]] self._backup_db() self._save_db() else: - print(f"\033[91m#bugs\033[0m Key '{key}' doesn't exist, cannot remove. Make sure the key path is correct.") + self.logger.error(f"\033[91m#bugs\033[0m Key '{key}' doesn't exist, cannot remove. Make sure the key path is correct.") # ================================================== # WHOLE DATABASE @@ -289,7 +290,7 @@ def get_subcollection(self, collection_name: str, item_id: Optional[str] = None) if item_id in collection: return collection[item_id] else: - print(f"\033[91m#bugs\033[0m ID '{item_id}' not found in collection '{collection_name}'. Check if the ID is correct; use get_subcollection('{collection_name}') to see all items.") + self.logger.error(f"\033[91m#bugs\033[0m ID '{item_id}' not found in collection '{collection_name}'. Check if the ID is correct; use get_subcollection('{collection_name}') to see all items.") return None return collection @@ -303,14 +304,14 @@ def set_subcollection(self, collection_name: str, item_id: str, value: Any) -> N value (Any): The value to set. """ if not self.validate_data(value): - print(f"\033[91m#bugs\033[0m Invalid data format. Your data should look like this: {{'name': 'Aliou', 'age': 30}}.") + self.logger.error(f"\033[91m#bugs\033[0m Invalid data format. Your data should look like this: {{'name': 'Aliou', 'age': 30}}.") return if collection_name not in self.db: self.db[collection_name] = {} if item_id in self.db[collection_name]: - print(f"\033[91m#bugs\033[0m ID '{item_id}' already exists in collection '{collection_name}'. Use db.edit_subcollection('{collection_name}', '{item_id}', new_value) to update or add new data.") + self.logger.error(f"\033[91m#bugs\033[0m ID '{item_id}' already exists in collection '{collection_name}'. Use db.edit_subcollection('{collection_name}', '{item_id}', new_value) to update or add new data.") return self.db[collection_name][item_id] = value @@ -327,7 +328,7 @@ def edit_subcollection(self, collection_name: str, item_id: str, value: Any) -> value (Any): The new value. """ if not self.validate_data(value): - print(f"\033[91m#bugs\033[0m Invalid data format. Your data should look like this: {{'name': 'Aliou', 'age': 30}}.") + self.logger.error(f"\033[91m#bugs\033[0m Invalid data format. Your data should look like this: {{'name': 'Aliou', 'age': 30}}.") return if collection_name in self.db and item_id in self.db[collection_name]: @@ -338,7 +339,7 @@ def edit_subcollection(self, collection_name: str, item_id: str, value: Any) -> self._backup_db() self._save_db() else: - print(f"\033[91m#bugs\033[0m ID '{item_id}' not found in collection '{collection_name}', cannot edit. Use 'set_subcollection' to create a new item.") + self.logger.error(f"\033[91m#bugs\033[0m ID '{item_id}' not found in collection '{collection_name}', cannot edit. Use 'set_subcollection' to create a new item.") def remove_subcollection(self, collection_name: str, item_id: Optional[str] = None) -> None: """ @@ -354,7 +355,7 @@ def remove_subcollection(self, collection_name: str, item_id: Optional[str] = No self._backup_db() self._save_db() else: - print(f"\033[91m#bugs\033[0m Collection '{collection_name}' not found, cannot remove. Make sure the collection name is correct.") + self.logger.error(f"\033[91m#bugs\033[0m Collection '{collection_name}' not found, cannot remove. Make sure the collection name is correct.") return else: if collection_name in self.db and item_id in self.db[collection_name]: @@ -362,5 +363,5 @@ def remove_subcollection(self, collection_name: str, item_id: Optional[str] = No self._backup_db() self._save_db() else: - print(f"\033[91m#bugs\033[0m ID '{item_id}' not found in collection '{collection_name}', cannot remove. Check the ID and collection name; use get_subcollection('{collection_name}') to see all items.") + self.logger.error(f"\033[91m#bugs\033[0m ID '{item_id}' not found in collection '{collection_name}', cannot remove. Check the ID and collection name; use get_subcollection('{collection_name}') to see all items.") return \ No newline at end of file diff --git a/LiteJsonDb/modules/__pycache__/__init__.cpython-311.pyc b/LiteJsonDb/modules/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..0a2521e Binary files /dev/null and b/LiteJsonDb/modules/__pycache__/__init__.cpython-311.pyc differ diff --git a/LiteJsonDb/modules/__pycache__/csv.cpython-311.pyc b/LiteJsonDb/modules/__pycache__/csv.cpython-311.pyc new file mode 100644 index 0000000..e60390e Binary files /dev/null and b/LiteJsonDb/modules/__pycache__/csv.cpython-311.pyc differ diff --git a/LiteJsonDb/modules/__pycache__/search.cpython-311.pyc b/LiteJsonDb/modules/__pycache__/search.cpython-311.pyc new file mode 100644 index 0000000..d2bb2d0 Binary files /dev/null and b/LiteJsonDb/modules/__pycache__/search.cpython-311.pyc differ diff --git a/LiteJsonDb/modules/__pycache__/tgbot.cpython-311.pyc b/LiteJsonDb/modules/__pycache__/tgbot.cpython-311.pyc new file mode 100644 index 0000000..0345922 Binary files /dev/null and b/LiteJsonDb/modules/__pycache__/tgbot.cpython-311.pyc differ diff --git a/LiteJsonDb/modules/csv.py b/LiteJsonDb/modules/csv.py index 0ec8aed..98228e9 100644 --- a/LiteJsonDb/modules/csv.py +++ b/LiteJsonDb/modules/csv.py @@ -1,5 +1,6 @@ import csv import os +import logging from typing import Dict, Any, Union class CSVExporter: @@ -42,5 +43,5 @@ def export(self, data: Union[Dict[str, Any], Any], filename: str = "export.csv") writer.writerows(data if isinstance(data, list) else [data]) return filepath except Exception as e: - print(f"\033[91m#bugs\033[0m CSV export error: {e}") + logging.getLogger('LiteJsonDb').error(f"\033[91m#bugs\033[0m CSV export error: {e}") return "" \ No newline at end of file diff --git a/LiteJsonDb/modules/search.py b/LiteJsonDb/modules/search.py index 31307f5..625dd60 100644 --- a/LiteJsonDb/modules/search.py +++ b/LiteJsonDb/modules/search.py @@ -6,9 +6,10 @@ ██████╔╝███████╗██║░░██║██║░░██║╚█████╔╝██║░░██║██╗██║░░░░░░░░██║░░░ ╚═════╝░╚══════╝╚═╝░░╚═╝╚═╝░░╚═╝░╚════╝░╚═╝░░╚═╝╚═╝╚═╝░░░░░░░░╚═╝░░░ """ +import logging from typing import Any, Dict, Optional -def search_data(data: Dict[str, Any], search_value: Any, key: Optional[str] = None) -> Dict[str, Any]: +def search_data(data: Dict[str, Any], search_value: Any, key: Optional[str] = None, substring: bool = False, case_sensitive: bool = True) -> Dict[str, Any]: """ Search for a value in a nested dictionary or within a specific key. @@ -16,6 +17,8 @@ def search_data(data: Dict[str, Any], search_value: Any, key: Optional[str] = No data (Dict[str, Any]): The dictionary to search within. search_value (Any): The value to search for. key (Optional[str]): If provided, search within this specific key. + substring (bool): If True, perform substring search. Defaults to False. + case_sensitive (bool): If False, perform case-insensitive search. Defaults to True. Returns: Dict[str, Any]: A dictionary containing matching results. @@ -36,21 +39,32 @@ def search_recursive(d: Any, value: Any, current_key: str = ''): new_key = f"{current_key}/{k}" if current_key else k if isinstance(v, dict): search_recursive(v, value, new_key) - elif value in (v, str(v)): - results[new_key] = v + else: + str_v = str(v) + search_str = str(value) + if not case_sensitive: + str_v = str_v.lower() + search_str = search_str.lower() + if substring: + if search_str in str_v: + results[new_key] = v + else: + if value == v or search_str == str_v: + results[new_key] = v elif isinstance(d, list): for index, item in enumerate(d): search_recursive(item, value, f"{current_key}/{index}") + logger = logging.getLogger('LiteJsonDb') if key: if key not in data: - print(f"\033[91m#bugs\033[0m Key '{key}' not found for search.") + logger.error(f"\033[91m#bugs\033[0m Key '{key}' not found for search.") else: search_recursive(data[key], search_value) else: search_recursive(data, search_value) if not results: - print(f"\033[90m#info\033[0m Value '{search_value}' not found.") + logger.info(f"\033[90m#info\033[0m Value '{search_value}' not found.") return results \ No newline at end of file diff --git a/LiteJsonDb/utility/__pycache__/__init__.cpython-311.pyc b/LiteJsonDb/utility/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1181fa8 Binary files /dev/null and b/LiteJsonDb/utility/__pycache__/__init__.cpython-311.pyc differ diff --git a/LiteJsonDb/utility/__pycache__/utils.cpython-311.pyc b/LiteJsonDb/utility/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..891d3a6 Binary files /dev/null and b/LiteJsonDb/utility/__pycache__/utils.cpython-311.pyc differ