一个并发安全的简单json文件的操作库。适合于完全独立的多个进程通过JSON文件的内容协调工作的场景。
json文件的内容对应一个简单的字典,字典元素的值是简单的数据类型,比如布尔值、数值、字符串,不能是字典、列表和集合等复合类型。
使用文件锁保证多进程并发操作安全。
要求读写json文件前,要先创建json文件。
支持日志记录,如果要记录日志,要先创建日志文件。
'''
author: Liu Yifan
一个并发安全的简单json文件的操作库。适合于完全独立的多个进程通过JSON文件的内容协调工作的场景。
json文件的内容对应一个简单的字典,字典元素的值是简单的数据类型,比如布尔值、数值、字符串,不能是字典、列表和集合等复合类型。
使用文件锁保证多进程并发操作安全。
要求读写json文件前,要先创建json文件。
支持日志记录,如果要记录日志,要先创建日志文件。
'''
import fcntl
import json
import datetime
from pathlib import Path
def create_file(file_path):
'''
在指定位置创建文件。如果父目录或者祖先目录不存在,则自动创建。如果文件已经存在,则更新修改时间。
可以用来创建json或者log文件。
'''
fold_path = Path(file_path).parent
# parents=True:如果父目录或者祖先目录不存在,则自动创建。exist_ok=True:忽略FileExistsError异常。
Path(fold_path).mkdir(parents=True, exist_ok=True)
# exist_ok=True:如果文件已经存在,则更新修改时间,而不是抛出FileExistsError异常。
Path(file_path).touch(exist_ok=True)
def read_json_file_field(file,key):
'''
从json文件中读取一个字段的值,使用key来标识字段
如果文件不存在,则抛出异常FileNotFoundError。
如果key不存在,则抛出异常KeyError。
'''
with open(file,"r") as f:
fcntl.flock(f, fcntl.LOCK_SH)
return json.load(f)[key]
def read_json_file(file):
'''
读取整个json文件的内容,返回一个字典。
如果文件不存在,则抛出异常FileNotFoundError。
'''
with open(file,"r") as f:
fcntl.flock(f, fcntl.LOCK_SH)
return json.load(f)
def write_log(file, log_file, operator_name, msg):
'''
记录log,方便回溯问题。
operator_name是操作者的名字。
如果json文件或者log文件不存在,则抛出异常FileNotFoundError。
'''
# 为了避免同时获取json_file和json_两把锁导致死锁的可能性,这里同一时间只获取一把锁
json_file_content = read_json_file(file)
with open(log_file,"a") as f:
fcntl.flock(f, fcntl.LOCK_EX)
f.write(f"{datetime.datetime.now()}: {operator_name} {msg}\n")
f.write(f" {json_file_content}\n")
def write_json_file(file, dict_, log_file=None, operator_name=None):
'''
将整个字典写入json_file,可以用于初始化操作,也可以用于json_file内容重置操作。
如果json文件不存在,会尝试创建文件,但是如果所属文件夹不存在,则抛出异常FileNotFoundError。
'''
with open(file,"w") as f:
fcntl.flock(f, fcntl.LOCK_EX)
json.dump(dict_,f)
if log_file:
write_log(file, log_file, operator_name, f"Write dict {dict_} into json file {file}")
def write_json_file_field(file,key,value,log_file=None,operator_name=None):
'''
修改指定元素的值。
如果key不存在则捎带添加了元素。
如果json文件不存在,则抛出异常FileNotFoundError。
'''
with open(file,"r+") as f:
fcntl.flock(f, fcntl.LOCK_EX)
dict_ = json.load(f)
dict_[key]=value
# 因为执行了json.load()之后,指针移动到了文件末尾。
# 这里需要重写文件的所有内容,所以先将指针移动文件头部,然后清除文件内容
f.seek(0)
f.truncate()
json.dump(dict_,f)
if log_file:
write_log(file, log_file, operator_name, f"set {key} to {value}")
def incr_json_file_field(file, key, incr_number=1, log_file=None, operator_name=None):
'''
将指定元素的值加上指定的值,默认加一。如果指定的是负数,则实际效果是减小数值。
如果key不存在,则首先添加元素,默认值为0。
如果json文件不存在,则抛出异常FileNotFoundError。
'''
with open(file,"r+") as f:
fcntl.flock(f, fcntl.LOCK_EX)
dict_ = json.load(f)
dict_.setdefault(key,0)
dict_[key] += incr_number
f.seek(0)
f.truncate()
json.dump(dict_,f)
if log_file:
write_log(file, log_file, operator_name, f"add one to {key}")