1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
# Cork - Authentication module for the Bottle web framework
# Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file.
# Released under LGPLv3+ license, see LICENSE.txt
"""
.. module:: json_backend
:synopsis: JSON file-based storage backend.
"""
from logging import getLogger
import os
import shutil
import sys
try:
import json
except ImportError: # pragma: no cover
import simplejson as json
from .base_backend import BackendIOException
is_py3 = (sys.version_info.major == 3)
log = getLogger(__name__)
try:
dict.iteritems
py23dict = dict
except AttributeError:
class py23dict(dict):
iteritems = dict.items
class BytesEncoder(json.JSONEncoder):
def default(self, obj):
if is_py3 and isinstance(obj, bytes):
return obj.decode()
return json.JSONEncoder.default(self, obj)
class JsonBackend(object):
"""JSON file-based storage backend."""
def __init__(self, directory, users_fname='users',
roles_fname='roles', pending_reg_fname='register', initialize=False):
"""Data storage class. Handles JSON files
:param users_fname: users file name (without .json)
:type users_fname: str.
:param roles_fname: roles file name (without .json)
:type roles_fname: str.
:param pending_reg_fname: pending registrations file name (without .json)
:type pending_reg_fname: str.
:param initialize: create empty JSON files (defaults to False)
:type initialize: bool.
"""
assert directory, "Directory name must be valid"
self._directory = directory
self.users = py23dict()
self._users_fname = users_fname
self.roles = py23dict()
self._roles_fname = roles_fname
self._mtimes = py23dict()
self._pending_reg_fname = pending_reg_fname
self.pending_registrations = py23dict()
if initialize:
self._initialize_storage()
self._refresh() # load users and roles
def _initialize_storage(self):
"""Create empty JSON files"""
self._savejson(self._users_fname, {})
self._savejson(self._roles_fname, {})
self._savejson(self._pending_reg_fname, {})
def _refresh(self):
"""Load users and roles from JSON files, if needed"""
self._loadjson(self._users_fname, self.users)
self._loadjson(self._roles_fname, self.roles)
self._loadjson(self._pending_reg_fname, self.pending_registrations)
def _loadjson(self, fname, dest):
"""Load JSON file located under self._directory, if needed
:param fname: short file name (without path and .json)
:type fname: str.
:param dest: destination
:type dest: dict
"""
try:
fname = "%s/%s.json" % (self._directory, fname)
mtime = os.stat(fname).st_mtime
if self._mtimes.get(fname, 0) == mtime:
# no need to reload the file: the mtime has not been changed
return
with open(fname) as f:
json_data = f.read()
except Exception as e:
raise BackendIOException("Unable to read json file %s: %s" % (fname, e))
try:
json_obj = json.loads(json_data)
dest.clear()
dest.update(json_obj)
self._mtimes[fname] = os.stat(fname).st_mtime
except Exception as e:
raise BackendIOException("Unable to parse JSON data from %s: %s" \
% (fname, e))
def _savejson(self, fname, obj):
"""Save obj in JSON format in a file in self._directory"""
fname = "%s/%s.json" % (self._directory, fname)
try:
with open("%s.tmp" % fname, 'w') as f:
json.dump(obj, f, cls=BytesEncoder)
f.flush()
shutil.move("%s.tmp" % fname, fname)
except Exception as e:
raise BackendIOException("Unable to save JSON file %s: %s" \
% (fname, e))
def save_users(self):
"""Save users in a JSON file"""
self._savejson(self._users_fname, self.users)
def save_roles(self):
"""Save roles in a JSON file"""
self._savejson(self._roles_fname, self.roles)
def save_pending_registrations(self):
"""Save pending registrations in a JSON file"""
self._savejson(self._pending_reg_fname, self.pending_registrations)
|