# -*- coding: utf-8 -*- __title__ = "config_ini" __author__ = 'Mehmet Karatay & "Saraswati" (ChatGPT)' __purpose__ = "INI tabanlı konfigürasyon ve değer depolama yardımcıları" __version__ = "0.2.0" __date__ = "2025-11-20" """ ebuild/io/config_ini.py Revision : 2025-11-20 Authors : Mehmet Karatay & "Saraswati" (ChatGPT) Amaç ----- Eski EdmConfig yapısının modernize edilmiş, hatalardan arındırılmış ama aynı API'yi koruyan sürümü. Bu modül: - INI dosyalarını ConfigParser ile yönetir - Eksik section/item gördüğünde otomatik oluşturur - Dosya değişimini (mtime) izleyerek reload imkanı verir - Basit bir kilit (lock) mekanizması ile aynı dosyaya birden fazla yazma girişimini sıraya sokar. Kullanım Örnekleri ------------------ - EdmConfig: cfg = EdmConfig("/home/karatay/ebuild/config.ini") port = cfg.item("serial", "ttyUSB0", "/dev/ttyUSB0") - ConfigDict: section = cfg.get_section("serial") tty0 = section.get_item("ttyUSB0") - KilitliDosya: log = KilitliDosya("/var/log/ebuild.log") log.yaz("merhaba dünya\\n") """ import os import time from configparser import ConfigParser import traceback as tb # eski davranışla uyum için bırakıldı # --------------------------------------------------------------------------- # ConfigDict – tek bir section'ın sözlük hali # --------------------------------------------------------------------------- class ConfigDict: """ Bir INI dosyasındaki tek bir section'ı sözlük benzeri interface ile kullanmaya yarar. - Anahtar yoksa, otomatik olarak section içine eklenir ve boş string ile başlatılır. - Değerler her zaman string olarak saklanır. """ def __init__(self, cfgfile: str, section_name: str): self.cfgfile = cfgfile self.section_name = section_name self.cfg = ConfigParser() self.cfg.read(cfgfile) if not self.cfg.has_section(section_name): self.cfg.add_section(section_name) self._write() self.section = dict(self.cfg.items(section_name)) def _write(self) -> None: """INI dosyasını diske yazar.""" with open(self.cfgfile, "w") as f: self.cfg.write(f) def get_item(self, item_name: str) -> str: """ Section içindeki bir item’ı döndürür. Yoksa item'i oluşturur, boş string ile başlatır. """ try: return self.section[item_name] except KeyError: # Yoksa ekle self.cfg.set(self.section_name, item_name, "") self._write() # Yeniden oku self.cfg.read(self.cfgfile) self.section = dict(self.cfg.items(self.section_name)) return self.section.get(item_name, "") def set_item(self, item_name: str, value) -> None: """ Section içindeki bir item’ın değerini günceller (string'e çevirerek). """ value = str(value) self.cfg.set(self.section_name, item_name, value) self._write() self.section[item_name] = value # --------------------------------------------------------------------------- # EdmConfig – bir INI dosyasını yöneten üst sınıf # --------------------------------------------------------------------------- class EdmConfig: """ Tek bir INI dosyası için üst seviye sarmalayıcı. Özellikler: - Dosya yoksa otomatik oluşturur. - item(section, name, default) ile değer okur; yoksa default yazar. - reload() ile mtime kontrolü yaparak dosya değişimini algılar. """ def __init__(self, cfg_file_name: str): self.fname = cfg_file_name # Dosya yoksa oluştur if not os.path.isfile(cfg_file_name): with open(cfg_file_name, "w") as f: f.write("") self.cfg = ConfigParser() self.cfg.read(cfg_file_name) # İlk yükleme zamanı try: self.originalTime = os.path.getmtime(cfg_file_name) except OSError: self.originalTime = None # --------------------------- # Reload mekanizması # --------------------------- def get_loadtime(self) -> float: """Ini dosyasının son yüklenme zamanını (mtime) döndürür.""" return self.originalTime def reload(self) -> bool: """ Dosya değişmişse yeniden okur, True döner. Değişmemişse False döner. """ if self.fname is None: return False try: current = os.path.getmtime(self.fname) except FileNotFoundError: return False if self.originalTime is None or current > self.originalTime: self.cfg.read(self.fname) self.originalTime = current return True return False # --------------------------- # Section & item işlemleri # --------------------------- def add_section(self, section: str) -> None: """Yeni bir section ekler (yoksa).""" if not self.cfg.has_section(section): self.cfg.add_section(section) self._write() def add_item(self, section: str, item: str, value: str = "") -> None: """ İlgili section yoksa oluşturur, item yoksa ekler ve default değerini yazar. """ if not self.cfg.has_section(section): self.add_section(section) if not self.cfg.has_option(section, item): self.cfg.set(section, item, str(value)) self._write() def set_item(self, section: str, name: str, value="") -> None: """ Belirli bir section/key için değeri ayarlar (string'e çevirir). """ self.add_section(section) self.cfg.set(section, name, str(value)) self._write() def item(self, section: str, name: str, default="") -> str: """ INI'den item okur; yoksa veya boşsa default değeri yazar ve onu döndürür. eski davranışla uyumlu: her zaman string döner. """ try: val = self.cfg.get(section, name).strip() if val == "": self.set_item(section, name, default) return str(default) return val except Exception: # Eski koddaki gibi stack trace istersen: # print(tb.format_exc()) self.add_item(section, name, default) return str(default) def get_items(self, section: str) -> dict: """Verilen section'daki tüm key/value çiftlerini dict olarak döndürür.""" return dict(self.cfg.items(section)) def get_section(self, section_name: str) -> ConfigDict: """ Verilen section için ConfigDict nesnesi döndürür. Section yoksa oluşturur. """ if not self.cfg.has_section(section_name): self.cfg.add_section(section_name) self._write() return ConfigDict(self.fname, section_name) def get_section_names(self): """INI içindeki tüm section isimlerini döndürür.""" return self.cfg.sections() def get_key_names(self, section_name: str = ""): """ section_name verilirse o section altındaki key listesi, verilmezse her section için key listelerini döndürür. """ if section_name: if not self.cfg.has_section(section_name): return [] return list(self.cfg[section_name].keys()) return {s: list(self.cfg[s].keys()) for s in self.cfg.sections()} def get_section_values(self, section_name: str = ""): """ section_name verilirse o section'ın dict halini, verilmezse tüm section'ların dict halini döndürür. """ if section_name: if not self.cfg.has_section(section_name): return {} return dict(self.cfg.items(section_name)) return {s: dict(self.cfg.items(s)) for s in self.cfg.sections()} def _write(self) -> None: """Ini dosyasını diske yazar.""" with open(self.fname, "w") as f: self.cfg.write(f) # --------------------------------------------------------------------------- # KilitliDosya – çok basit file lock mekanizması # --------------------------------------------------------------------------- class KilitliDosya: """ Çok basit bir dosya kilit mekanizması. Aynı dosyaya birden fazla sürecin/yazmanın çakışmasını azaltmak için `fname.LCK` dosyasını lock olarak kullanır. """ def __init__(self, fname: str): self.fname = fname def _lockfile(self) -> str: return f"{self.fname}.LCK" def kontrol(self) -> bool: """Lock dosyası var mı? True/False.""" return os.path.exists(self._lockfile()) def kilitle(self) -> bool: """ Lock almaya çalışır. Lock yoksa oluşturur ve True döner. Varsa False döner. """ if not self.kontrol(): with open(self._lockfile(), "w") as f: f.write(" ") return True return False def kilit_ac(self) -> None: """Lock dosyasını kaldırır (yoksa sessizce geçer).""" try: os.remove(self._lockfile()) except FileNotFoundError: pass def oku(self): """Ana dosyayı satır satır okur ve liste döndürür.""" with open(self.fname, "r") as f: return f.readlines() def yaz(self, text: str) -> bool: """ Dosyaya kilitleyerek ekleme yapar. Lock alamazsa 10 deneme yapar, her seferinde 0.2s bekler. """ for _ in range(10): if self.kilitle(): try: with open(self.fname, "a") as f: f.write(text) finally: self.kilit_ac() return True time.sleep(0.2) return False # --------------------------------------------------------------------------- # Modül test / örnek kullanım # --------------------------------------------------------------------------- if __name__ == "__main__": # Basit smoke-test test_ini = "test_config.ini" cfg = EdmConfig(test_ini) # Birkaç değer dene port = cfg.item("serial", "ttyUSB0", "/dev/ttyUSB0") mode = cfg.item("general", "mode", "auto") print("serial.ttyUSB0 =", port) print("general.mode =", mode) # KilitliDosya testi log = KilitliDosya("test_log.txt") log.yaz("Config_ini self test OK\n")