【问题标题】:How to check if a SQLite3 database exists in Python?如何检查 Python 中是否存在 SQLite3 数据库?
【发布时间】:2012-10-07 14:38:24
【问题描述】:

我正在尝试在 Python 2.7.3 中创建一个函数来打开 SQLite 数据库。

这是我现在的代码:

import sqlite3 as lite
import sys

db = r'someDb.sqlite'

def opendb(db):
    try:
        conn = lite.connect(db)
    except sqlite3.Error:
        print "Error open db.\n"
        return False
    cur = conn.cursor()
    return [conn, cur]

我已经尝试了上面的代码,并且我观察到sqlite3 库会打开声明的数据库(如果存在),或者如果该数据库不存在则创建一个新数据库。

有没有办法使用sqlite3 方法检查数据库是否存在,或者我必须使用像os.path.isfile(path) 这样的文件操作?

【问题讨论】:

    标签: python python-2.7 sqlite


    【解决方案1】:

    在 Python 2 中,您必须使用 os.path.isfile 显式测试是否存在:

    if os.path.isfile(db):
    

    无法强制sqlite3.connect 函数不为您创建文件。


    对于使用 Python 3.4 或更新版本的用户,您可以使用更新的 URI 路径功能在打开数据库时设置不同的模式。 sqlite3.connect() 函数默认会在rwc 下打开数据库,即Read、Write & Create 模式,因此连接到不存在的数据库会导致它被创建。

    使用 URI,您可以指定不同的模式;如果您将其设置为rw,即Read & Write 模式,则在尝试连接到不存在的数据库时会引发异常。连接时设置uri=True标志可以设置不同的模式,传入file: URI,并在路径中添加mode=rw查询参数:

    from urllib.request import pathname2url
    
    try:
        dburi = 'file:{}?mode=rw'.format(pathname2url(db))
        conn = lite.connect(dburi, uri=True)
    except sqlite3.OperationalError:
        # handle missing database case
    

    有关接受哪些参数的更多详细信息,请参阅SQLite URI Recognized Query Parameters documentation

    【讨论】:

    • @JurkoGospodnetić:我必须注意这个问题是用python-2.7 标记的,所以URI 选项是在那儿不可用
    • @MartijnPieters - 是的,当我最初遇到这个问题时,我没有注意到
    【解决方案2】:

    os.path.isfile() 只是告诉你一个文件是否存在,而不是它是否存在并且是一个 SQLite3 数据库!知道http://www.sqlite.org/fileformat.html,你可以这样做:

    def isSQLite3(filename):
        from os.path import isfile, getsize
    
        if not isfile(filename):
            return False
        if getsize(filename) < 100: # SQLite database file header is 100 bytes
            return False
    
        with open(filename, 'rb') as fd:
            header = fd.read(100)
    
        return header[:16] == 'SQLite format 3\x00'
    

    然后像这样使用它:

    for file in files:
        if isSQLite3(file):
            print "'%s' is a SQLite3 database file" % file
        else:
            print "'%s' is not a SQLite3 database file" % file
    

    【讨论】:

    • 对于 Python 3,确保与字节进行比较:Header[0:16] == b'SQLite format 3\000'(注意格式字符串的前导 b
    • 不确定这里有什么附加值。唯一无法测试的情况是文件在连接之前是否存在。如果文件存在并且从连接中抛出异常,我也知道数据库是否有效。魔术字符串的存在仍然不能说连接会成功。那么这样做的目的是什么?
    • 这个肯定是答案。文件名或扩展名没有任何意义(特别是在 SQLite 上)。验证数据库标头是您可以在这种情况下应用的最安全的技术。
    【解决方案3】:

    是的,有一种方法可以用 Python 3.4+ 做你想做的事。

    使用sqlite3.connect()函数进行连接,但传递一个URI而不是文件路径,并将mode=rw添加到它的查询字符串中。

    这是一个完整的工作代码示例:

    import sqlite3
    con = sqlite3.connect('file:aaa.db?mode=rw', uri=True)
    

    这将从当前文件夹中名为 aaa.db 的文件打开现有数据库,但如果该文件无法打开或不存在,则会引发错误:

    Traceback (most recent call last):
      File "aaa.py", line 2, in <module>
        con = sqlite3.connect('file:aaa.db?mode=rw', uri=True)
    sqlite3.OperationalError: unable to open database file
    

    Python sqlite.connect() docs 声明:

    如果 uri 为真,则数据库被解释为 URI。这允许您指定选项。例如,要以只读模式打开数据库,您可以使用:

    db = sqlite3.connect('file:path/to/database?mode=ro', uri=True)

    可以在SQLite URI documentation 中找到有关此功能的更多信息,包括已识别选项的列表。

    这是从http://www.sqlite.org/c3ref/open.html收集的所有相关URI选项信息的摘录:

    mode:mode 参数可以设置为“ro”、“rw”、“rwc”或“memory”。尝试将其设置为任何其他值是错误的。如果指定了“ro”,则打开数据库以进行只读访问,就像在 sqlite3_open_v2() 的第三个参数中设置了 SQLITE_OPEN_READONLY 标志一样。如果模式选项设置为“rw”,则打开数据库进行读写(但不是创建)访问,就像设置了 SQLITE_OPEN_READWRITE(但不是 SQLITE_OPEN_CREATE)一样。值“rwc”等效于同时设置 SQLITE_OPEN_READWRITE 和 SQLITE_OPEN_CREATE。如果模式选项设置为“内存”,则使用从不从磁盘读取或写入的纯内存数据库。为 mode 参数指定一个比在第三个参数中传递给 sqlite3_open_v2() 的标志所指定的限制更少的值是错误的。

    sqlite3_open_v2() 接口的工作方式与 sqlite3_open() 类似,只是它接受两个附加参数以对新数据库连接进行额外控制。 sqlite3_open_v2() 的 flags 参数可以采用以下三个值之一,可选择与 SQLITE_OPEN_NOMUTEX、SQLITE_OPEN_FULLMUTEX、SQLITE_OPEN_SHAREDCACHE、SQLITE_OPEN_PRIVATECACHE 和/或 SQLITE_OPEN_URI 标志组合:

    SQLITE_OPEN_READONLY 数据库以只读模式打开。如果数据库不存在,则返回错误。

    SQLITE_OPEN_READWRITE 如果可能,打开数据库进行读取和写入,或者仅在文件受操作系统写保护时才读取。无论哪种情况,数据库必须已经存在,否则返回错误。

    SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE 数据库以读写方式打开,如果数据库不存在则创建。这是 sqlite3_open() 和 sqlite3_open16() 始终使用的行为。

    为方便起见,这里还有一个 Python 3.4+ 函数,用于将常规路径转换为 ​​sqlite.connect() 可用的 URI:

    import pathlib
    import urllib.parse
    
    def _path_to_uri(path):
        path = pathlib.Path(path)
        if path.is_absolute():
            return path.as_uri()
        return 'file:' + urllib.parse.quote(path.as_posix(), safe=':/')
    

    【讨论】:

    • OP:Python 2.7.3 不是 Python 3.x
    • @Nick - 是的,正如上面一些较早的 cmets 中提到的,当我最初遇到这个问题时,我没有注意到,我在这里发布的信息确实发现很好用了好几次然后:-)
    【解决方案4】:

    这是一个基于 Tom Horen 的答案 的分叉(使用 Python 3),它提供了一个比所选答案更完整和可靠的解决方案。

    选择的答案,不评估任何内容、标题等,以确定文件是否实际包含与 SQLite3 数据库相关的任何数据。

    我试图在这里提出一些更务实的东西:

    #!/usr/bin/python3
    
    import os
    import sys
    
    if os.path.isfile('test.sqlite3'):
        if os.path.getsize('test.sqlite3') > 100:
            with open('test.sqlite3','r', encoding = "ISO-8859-1") as f:
                header = f.read(100)
                if header.startswith('SQLite format 3'):
                    print("SQLite3 database has been detected.")
    

    【讨论】:

      【解决方案5】:

      基于上面的其他几个答案。这是一个适用于Python 3.7.7 的干净解决方案:

      def isSqlite3Db(db):
          if not os.path.isfile(db): return False
          sz = os.path.getsize(db)
      
          # file is empty, give benefit of the doubt that its sqlite
          # New sqlite3 files created in recent libraries are empty!
          if sz == 0: return True
      
          # SQLite database file header is 100 bytes
          if sz < 100: return False
          
          # Validate file header
          with open(db, 'rb') as fd: header = fd.read(100)    
      
          return (header[:16] == b'SQLite format 3\x00')
      

      用法:

      if isSqlite3Db('<path_to_db>'):
          # ... <path_to_db> is a Sqlite 3 DB
      

      注意事项:

      • 检查文件大小大于 100 的答案不起作用,因为在最近的 python 中创建的新 sqlite3 db 会创建长度为 0 的文件。
      • 读取文件头的其他示例返回 Python 3.7.7 中的字节而不是字符串,因此比较会失败。
      • 使用 sqlite3.connect(dburl, uri=True) 的示例在 Python 3.7.7 中对我不起作用,因为它会产生误报。

      【讨论】:

        【解决方案6】:

        我在脚本开头使用了如下函数,以便尝试找出 sqlite3 db 脚本可能无法正常工作的原因。就像 cmets 说的那样,它使用 3 个阶段,检查路径是否存在,检查路径是否为文件,检查该文件的标头是否为 sqlite3 标头。

        def checkdbFileforErrors():
        
            #check if path exists
            try:
                with open('/path/to/your.db'): pass
            except IOError:
                return 1
        
            #check if path if a file
            if not isfile('/path/to/your.db'):
                return 2
        
            #check if first 100 bytes of path identifies itself as sqlite3 in header
            f = open('/path/to/your.db', "rx")
            ima = f.read(16).encode('hex')
            f.close()
            #see http://www.sqlite.org/fileformat.html#database_header magic header string
            if ima != "53514c69746520666f726d6174203300": 
                return 3
        
            return 0
        

        【讨论】:

        • 如果 ima == '53514c69746520666f726d6174203300' 应该是这样的:return 3
        • 这种为每个错误返回数字或成功返回 0 的设计比 Python 更像 C 风格。 Python 更多的是关于抛出异常。
        猜你喜欢
        • 2013-09-19
        • 2018-04-22
        • 2021-06-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-16
        • 1970-01-01
        相关资源
        最近更新 更多