Python Sqlite数据库与配置文件的加载、编辑和保存

一、Sqlite数据库

SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。SQLite第一个Alpha版本诞生于2000年5月。 至2021年已经接近有21个年头,SQLite也迎来了一个版本 SQLite 3已经发布。

以下是一个已经封装好的数据库类:

import sqlite3
import random
import time
import traceback

__all__ = ["Sql"]

def dict_str(d):
    return ",".join([" ".join([k,v]) for k,v in d.items()])

# 连接数据库
class Sql:
    def __init__(self,db,autoCommit = False):
        self.conn = sqlite3.connect(db)
        self.cur = self.conn.cursor()
        self.conn.execute("BEGIN")
        self.autoCommit = autoCommit
    def createTable(self,name:str,headers:dict,debug = False): # 创建名为name的表,包含headers数据列,如果debug启用则当表存在时不会报错
        self.cur.execute(f"CREATE TABLE {'if not exists ' if debug else ''}{name}({dict_str(headers)});")
        if self.autoCommit:self.commit()
    def delTable(self,table:str): # 删除名为table的表
        self.cur.execute(f"DROP TABLE {table}")
        if self.autoCommit:self.commit()
    def columnCount(self,table): # 查询table表中的列数
        self.cur.execute(f"SELECT COUNT(*) FROM {table}")
        if self.autoCommit:self.commit()
        return self.cur.fetchone()[0]
    def insertValue(self,table:str,data:tuple): # 插入单行数据
        self.cur.execute(f"INSERT INTO {table} VALUES ({('?,' * len(data))[:-1]});",data)
        if self.autoCommit:self.commit()
    def insertValues(self,table:str,data:list): # [(1,2,3),(4,5,6),...],插入多行数据
        self.cur.executemany(f"INSERT INTO {table} VALUES ({('?,' * len(data[0]))[:-1]});",data)
        if self.autoCommit:self.commit()
    def updateValue(self,table:str,upd_header:str,upd_value:str,header:str,value:str): # 设置table表中upd_header列值为upd_value的行中header列的数据为value
        self.cur.execute(f"UPDATE {table} set {header} = '{value}' WHERE {upd_header} = '{upd_value}'")
        if self.autoCommit:self.commit()
    def delValue(self,table:str,header:str,value:str): # 删除table表中header列值为value的整行数据
        self.cur.execute(f"DELETE FROM {table} WHERE {header} = '{value}'")
        if self.autoCommit:self.commit()
    def delAll(self,table:str): # 删除某表中所有数据
        self.cur.execute(f"DELETE FROM {table}")
        if self.autoCommit:self.commit()
    def getTableList(self): # 读取所有表列表
        return [i[1] for i in self.getAll("sqlite_master")]
    def getAll(self,table): # 读取整个表
        self.cur.execute(f"SELECT * FROM {table}")
        return self.cur.fetchall()
    def getColumn(self,table,header): # 读取某列
        self.cur.execute(f"SELECT {header} FROM {table}")
        return self.cur.fetchall()
    def getRow(self,table,row): # 查询一行
        self.cur.execute(f"SELECT * FROM {table} LIMIT {row},1;")
        return self.cur.fetchall()[0]
    def getRows(self,table,row0,row1): # 查询多行(包括row1),返回生成器
        return map(lambda x:getRow(table,x),range(row0,row1 + 1))
    def sectionGetAll(self,table,block_size = 1000): # 分块读取整个表
        offset = 0
        while True:
            # 查询数据
            query = f"SELECT * FROM {table} LIMIT {offset}, {block_size}"
            self.cur.execute(query)
            data = self.cur.fetchall()
            # 如果没有更多数据,跳出循环
            if not data:
                break
            yield data
            # 更新偏移量
            offset += block_size
    
    def copyDbTableToNowDb(self,dbpath,dbTable,nowTable):
        self.cur.execute(f"ATTACH DATABASE '{dbpath}' AS copyTmpDb")
        self.cur.execute(f"CREATE TABLE copyTmpDb.{dbTable} AS SELECT * FROM {nowTable}")
        self.cur.execute(f"DETACH DATABASE copyTmpDb")
        if self.autoCommit:self.commit()
    def rollback(self): # 回滚更改
        self.conn.rollback()
    def commit(self): # 提交更改
        self.conn.commit()
    def close(self,commit = True): # 关闭数据库
        self.conn.commit()
        self.cur.close()
        self.conn.close()

 可以将其放到自己的项目文件夹里直接进行调用,更加方便。

二、配置文件

Configure是已经封装好的配置类:

import json
import os
import sys
from PyQt5.QtWidgets import QMessageBox,QWidget

__all__ = ["MessageBox","Configure"]

def infoget(text):
    print(text)

class MessageBox: # 可以改成你的报错弹窗,如使用easygui、tkinter等
    class ButtonType:
        Yes = QMessageBox.Yes
        No = QMessageBox.No
        Cancel = QMessageBox.Cancel
    
    def _result(self,result):
        if QMessageBox.Yes == result:
            return self.ButtonType.Yes
        elif QMessageBox.No == result:
            return self.ButtonType.No
        elif QMessageBox.Cancel == result:
            return self.ButtonType.Cancel
        else:
            return None
    parent = QWidget()
    def info(self,title = "信息",text = "",buttons = ButtonType.Yes,defaultButton = ButtonType.Yes):
        infoget(title.join(["MessageBox-info",":"]),text,type = "Info")
        return self._result(QMessageBox.information(self.parent,title,text,buttons = buttons,defaultButton = defaultButton))
        
    def warn(self,title = "警告",text = "",buttons = ButtonType.Yes,defaultButton = ButtonType.Yes):
        infoget(title.join(["MessageBox-warn",":"]),text,type = "Warn")
        return self._result(QMessageBox.warning(self.parent,title,text,buttons = buttons,defaultButton = defaultButton))
    
    def err(self,title = "错误",text = "",buttons = ButtonType.Yes,defaultButton = ButtonType.Yes):
        infoget(title.join(["MessageBox-error",":"]),text,type = "Error")
        return self._result(QMessageBox.critical(self.parent,title,text,buttons = buttons,defaultButton = defaultButton))
    
    def question(self,title = "提问",text = "",buttons = ButtonType.Yes | ButtonType.No,defaultButton = ButtonType.Yes):
        infoget(title.join(["MessageBox-question",":"]),text,type = "Info")
        return self._result(QMessageBox.question(self.parent,title,text,buttons = buttons,defaultButton = defaultButton))

MessageBox = MessageBox()

class Configure:
    normalConfig = {}
    def __init__(self,file = "Config.json",create = True):
        self._state = 0
        self._file = file
        self._readConfigFile(create)
    
    def __str__(self):
        return "Configure({})".format(",".join([f"{k}={v}" for k,v in self._cfgdic.copy().items()])).replace("\n","\\n")
    
    def _clearKey(self):
        if hasattr(self,"_keyList"):
            list(map(lambda x:(delattr(self,x),self._keyList.remove(x)),self._keyList))
    
    def _dealConfigToDict(self,content): # str转dict(读取的配置)
        try:
            return json.loads(content)
        except json.JSONDecodeError as err:
            MessageBox.err(text = str(err))
            return
        except Exception as err:
            MessageBox.err("未知错误",str(err))
            return
    def _dealConfigToStr(self,cfgdic): # dict转str(写入的配置)
        try:
            return json.dumps(cfgdic,ensure_ascii = False)
        except Exception as err:
            MessageBox.err("未知错误",f"加载配置字典到json字符串时发生错误:\n详细信息: {str(err)}")
            return False
    def _readConfigFile(self,create):
        self._clearKey()
        is_file_can = True
        is_file_exists = True
        if not isinstance(self._file,str):
            MessageBox.err("软件错误","传入参数file数据类型错误")
            is_file_can = False
        elif not os.path.exists(self._file):
            if not create:
                MessageBox.err(text = f"配置文件\"{self._file}\"不存在")
            else:
                is_file_exists = False
            is_file_can = False
        elif not os.path.isfile(self._file):
            MessageBox.err("文件错误",f"配置文件\"{self._file}\"不是一个有效的文件")
            is_file_can = False
        
        if is_file_can or create:
            try:
                self._file = os.path.abspath(self._file)
            except Exception as err:
                MessageBox.err(text = "获取配置文件绝对路径失败\n详细信息: {err}")
                return
            if is_file_exists:
                try:
                    with open(self._file,"r",encoding = "utf-8") as f:
                        content = f.read()
                except PermissionError as err:
                    MessageBox.err("读取文件失败",f"没有权限读取配置文件\"{self._file}\"\n详细信息: {str(err)}")
                    return
                except Exception as err:
                    MessageBox.err("未知错误",str(err))
                    return
                
                self._cfgdic = self._dealConfigToDict(content)
                if not self._cfgdic:
                    return
                self._loadCfgdic()
            else:
                self._cfgdic = self.normalConfig.copy()
                self._loadCfgdic()
                self.SaveConfigToFile()
            
            self._state = 1
    def _loadCfgdic(self):
        self._keyList = []
        try:
            for k,v in self._cfgdic.copy().items():
                if not isinstance(k,str):
                    self._clearKey()
                    MessageBox.err("配置文件错误",f"键\"{k}\"不是一个有效的键")
                    return
                elif k in self._keyList:
                    self._clearKey()
                    MessageBox.err("配置文件错误",f"键\"{k}\"有重复项")
                    return
                elif k.startswith("_"):
                    self._clearKey()
                    MessageBox.err("配置文件错误",f"键\"{k}\"不能以下划线开头")
                    return
                elif k in ("GetConfigState",
                           "GetKeyList",
                           "GetConfigFilePath",
                           "SaveConfigToFile"):
                    self._clearKey()
                    MessageBox.err("配置文件错误",f"键\"{k}\"与类函数名重名")
                    return
                else:
                    self._keyList.append(k)
                    setattr(self,k,v)
        except Exception as err:
            self._clearKey()
            MessageBox.err("未知错误",str(err))
            return
    def GetConfigState(self):
        return self._state
    def GetKeyList(self):
        return self._keyList
    def GetConfigFilePath(self):
        return self._file
    def SaveConfigToFile(self,file = None):
        if file == None:
            file = self._file
        
        if not isinstance(file,str):
            MessageBox.err("软件错误","file数据类型错误")
            return False
        elif (not os.path.isfile(file)) and os.path.exists(file):
            MessageBox.err("文件错误",f"配置文件\"{file}\"不是一个有效的文件")
            return False
        else:
            try:
                file = os.path.abspath(file)
            except Exception as err:
                MessageBox.err(text = "获取配置文件绝对路径失败\n详细信息: {err}")
                return False
            
            try:
                content = {k:getattr(self,k) for k in self._keyList.copy()}
            except Exception as err:
                MessageBox.err("未知错误",f"加载配置字典时发生错误:\n详细信息: {str(err)}")
                return False
            
            content = self._dealConfigToStr(content)
            
            try:
                with open(file,"w",encoding = "utf-8") as f:
                    f.write(content)
            except PermissionError as err:
                MessageBox.err("写入文件失败",f"没有权限写入配置文件\"{file}\"\n详细信息: {str(err)}")
                return False
            except Exception as err:
                MessageBox.err("未知错误",str(err))
                return False
        
        return True

修改Configure中的_dealConfigToDict和_dealConfigToStr函数可以更改读取配置文件的方式,这个例子读取的是json格式的配置文件 。


End

制作不易,感谢大家的关注与支持!

你可能感兴趣的:(数据库,sqlite,python,json,pyqt)