Skip to content

Commit

Permalink
Merge pull request #104 from yahoo/handle_dbfilename_with_spaces
Browse files Browse the repository at this point in the history
Handle dbfilename with spaces
  • Loading branch information
dwighthubbard committed May 12, 2016
2 parents bd11122 + ba0b9ff commit b01734a
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 81 deletions.
13 changes: 1 addition & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,13 @@ Installing on Mac OSX
Redislite for OSX comes as a wheel package by default that can be installed
using current versions of pip.

To install Redislite on MacOSX using the sdist package instead you may need
To install Redislite on MacOSX using the sdist package instead you will need
the XCode command line utilities installed. If you do not have xcode
installed on recent OSX releases they can be installed by
running::

xcode-select --install

Note redislite and its dependencies use the gcc compiler. On OSX you may run
into errors indicating that your machine is using clang to compile instead, for
example::

clang: error: unknown argument: '-mno-fused-madd' [-Wunused-command-line-argument-hard-error-in-future]

If this is the case, set your environment variable to override the use of clang
in favor of gcc::

CC=gcc


Installation
============
Expand Down
90 changes: 77 additions & 13 deletions redislite/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _cleanup(self, sys_modules=None):
Stop the redis-server for this instance if it's running
:return:
"""
if sys_modules:
if sys_modules: # pragma: no cover
import sys
sys.modules.update(sys_modules)

Expand All @@ -81,7 +81,7 @@ def _cleanup(self, sys_modules=None):
)
# noinspection PyUnresolvedReferences
logger.debug(
'Shutting down redis server with pid of %d', self.pid
'Shutting down redis server with pid of %r', self.pid
)
self.shutdown()
self.socket_file = None
Expand Down Expand Up @@ -178,10 +178,7 @@ def _start_redis(self):
rc = subprocess.call(command)
if rc: # pragma: no cover
logger.debug('The binary redis-server failed to start')
redis_log = os.path.join(self.redis_dir, 'redis.log')
if os.path.exists(redis_log):
with open(redis_log) as file_handle:
logger.debug(file_handle.read())
logger.debug('Redis Server log:\n%s', self.redis_log)
raise RedisLiteException('The binary redis-server failed to start')

# Wait for Redis to start
Expand All @@ -192,11 +189,13 @@ def _start_redis(self):
break
time.sleep(.1)
if timeout: # pragma: no cover
logger.debug('Redis Server log:\n%s', self.redis_log)
raise RedisLiteServerStartError(
'The redis-server process failed to start'
)

if not os.path.exists(self.socket_file): # pragma: no cover
logger.debug('Redis Server log:\n%s', self.redis_log)
raise RedisLiteException(
'Redis socket file %s is not present' % self.socket_file
)
Expand Down Expand Up @@ -283,14 +282,15 @@ def _load_setting_registry(self):
logger.debug('loading settings, found: %s', settings)
pidfile = settings.get('pidfile', '')
if os.path.exists(pidfile):
# noinspection PyUnusedLocal
pid_number = 0
with open(pidfile) as fh:
pid_number = int(fh.read())
if pid_number:
process = psutil.Process(pid_number)
if not process.is_running(): # pragma: no cover
logger.warn(
'Loaded registry for non-existant redis-server'
'Loaded registry for non-existent redis-server'
)
return
else: # pragma: no cover
Expand Down Expand Up @@ -329,6 +329,7 @@ def __init__(self, *args, **kwargs):
# If the user is specifying settings we can't configure just pass the
# request to the redis.Redis module
if 'host' in kwargs.keys() or 'port' in kwargs.keys():
# noinspection PyArgumentList
super(RedisMixin, self).__init__(
*args, **kwargs
) # pragma: no cover
Expand Down Expand Up @@ -362,9 +363,9 @@ def __init__(self, *args, **kwargs):
self.dbfilename = os.path.basename(db_filename)
self.dbdir = os.path.dirname(db_filename)

self.settingregistryfile = os.path.join(
self.settingregistryfile = repr(os.path.join(
self.dbdir, self.dbfilename + '.settings'
)
)).strip("'")

logger.debug('Setting up redis with rdb file: %s', self.dbfilename)
logger.debug('Setting up redis with socket file: %s', self.socket_file)
Expand All @@ -379,15 +380,15 @@ def __init__(self, *args, **kwargs):

if not self.dbdir:
self.dbdir = self.redis_dir
self.settingregistryfile = os.path.join(
self.settingregistryfile = repr(os.path.join(
self.dbdir, self.dbfilename + '.settings'
)

)).strip("'")
self._start_redis()

kwargs['unix_socket_path'] = self.socket_file
# noinspection PyArgumentList
logger.debug('Calling binding with %s, %s', args, kwargs)
# noinspection PyArgumentList
super(RedisMixin, self).__init__(*args, **kwargs) # pragma: no cover

logger.debug("Pinging the server to ensure we're connected")
Expand All @@ -396,6 +397,60 @@ def __init__(self, *args, **kwargs):
def __del__(self):
self._cleanup() # pragma: no cover

def redis_log_tail(self, lines=1, width=80):
"""
The redis log output
Parameters
----------
lines : int, optional
Number of lines from the end of the logfile to return, a value of
0 will return all lines, default=1
width : int, optional
The expected average width of a log file line, this is used to
determine the chunksize of the seek operations, default=80
Returns
-------
list
List of strings containing the lines from the logfile requested
"""
chunksize = lines * width

if not os.path.exists(self.logfile):
return []

with open(self.logfile) as log_handle:
if lines == 0:
return [l.strip() for l in log_handle.readlines()]
log_handle.seek(0, 2)
log_size = log_handle.tell()
if log_size == 0:
logger.debug('Logfile %r is empty', self.logfile)
return []
data = []
for increment in range(1, int(log_size / chunksize) + 1):
seek_location = max(chunksize * increment, 0)
log_handle.seek(seek_location, 0)
data = log_handle.readlines()
if len(data) >= lines:
return [l.strip() for l in data[-lines:]]
return [l.strip() for l in data]

@property
def redis_log(self):
"""
Redis server log content as a string
Returns
-------
str
Log contents
"""
return os.linesep.join(self.redis_log_tail(lines=0))

@property
def db(self):
"""
Expand Down Expand Up @@ -432,6 +487,7 @@ def pid(self):
return 0 # pragma: no cover


# noinspection PyUnresolvedReferences
class Redis(RedisMixin, redis.Redis):
"""
This class provides an enhanced version of the :class:`redis.Redis()` class
Expand Down Expand Up @@ -517,21 +573,29 @@ class are supported.
Attributes
----------
db : string
db : str
The fully qualified filename associated with the redis dbfilename
configuration setting. This attribute is read only.
logfile : str
The name of the redis-server logfile
pid :int
Pid of the running embedded redis server, this attribute is read
only.
redis_log : str
The contents of the redis-server log file
start_timeout : float
Number of seconds to wait for the redis-server process to start
before generating a RedisLiteServerStartError exception.
"""
pass


# noinspection PyUnresolvedReferences
class StrictRedis(RedisMixin, redis.StrictRedis):
"""
This class provides an enhanced version of the :class:`redis.StrictRedis()`
Expand Down
66 changes: 45 additions & 21 deletions redislite/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,48 @@
'activerehashing': 'yes',
'aof-rewrite-incremental-fsync': 'yes',
'appendonly': 'no',
'appendfilename': '"appendonly.aof"',
'appendfilename': 'appendonly.aof',
'appendfsync': 'everysec',
'aof-load-truncated': 'yes',
'auto-aof-rewrite-percentage': '100',
'auto-aof-rewrite-min-size': '64mb',
'bind': None,
'daemonize': 'yes',
'databases': '16',
'dbdir': './',
'dbfilename': 'redis.db',
'hash-max-ziplist-entries': '512',
'hash-max-ziplist-value': '64',
'hll-sparse-max-bytes': '3000',
'hz': '10',
'list-max-ziplist-entries': '512',
'list-max-ziplist-value': '64',
'loglevel': 'notice',
'logfile' : 'redis.log',
'lua-time-limit': '5000',
'pidfile': '/var/run/redislite/redis.pid',
'port': '0',
'save': ['900 1', '300 100', '60 200', '15 1000'],
'stop-writes-on-bgsave-error': 'yes',
'tcp-backlog': '511',
'unixsocket': '/var/run/redislite/redis.socket',
'unixsocketperm': '700',
'tcp-keepalive': '0',
'loglevel': 'notice',
'logfile' : 'redis.log',
'stop-writes-on-bgsave-error': 'yes',
'rdbcompression': 'yes',
'rdbchecksum': 'yes',
'timeout': '0',
'slave-serve-stale-data': 'yes',
'slave-read-only': 'yes',
'repl-disable-tcp-nodelay': 'no',
'slave-priority': '100',
'no-appendfsync-on-rewrite': 'no',
'aof-load-truncated': 'yes',
'lua-time-limit': '5000',
'slowlog-log-slower-than': '10000',
'slowlog-max-len': '128',
'latency-monitor-threshold': '0',
'notify-keyspace-events': '""',
'hash-max-ziplist-entries': '512',
'hash-max-ziplist-value': '64',
'list-max-ziplist-entries': '512',
'list-max-ziplist-value': '64',
'set-max-intset-entries': '512',
'timeout': '0',
'unixsocket': '/var/run/redislite/redis.socket',
'unixsocketperm': '700',
'zset-max-ziplist-entries': '128',
'zset-max-ziplist-value': '64',
'hll-sparse-max-bytes': '3000',
'hz': '10',
}


Expand Down Expand Up @@ -88,6 +88,30 @@ def settings(**kwargs):
return new_settings


def config_line(setting, value):
"""
Generate a single configuration line based on the setting and value
Parameters
----------
setting : str
The configuration setting
value : str
The value for the configuration setting
Returns
-------
str
The configuration line based on the setting and value
"""
if setting in [
'appendfilename', 'dbfilename', 'dbdir', 'dir', 'pidfile', 'unixsocket'
]:
value = repr(value)
return '{setting} {value}'.format(setting=setting, value=value)


def config(**kwargs):
"""
Generate a redis configuration file based on the passed arguments
Expand All @@ -109,13 +133,13 @@ def config(**kwargs):
if config_dict[key]:
if isinstance(config_dict[key], list):
for item in config_dict[key]:
configuration += '{key} {value}\n'.format(
key=key, value=item
)
configuration += config_line(
setting=key, value=item
) + '\n'
else:
configuration += '{key} {value}\n'.format(
key=key, value=config_dict[key]
)
configuration += config_line(
setting=key, value=config_dict[key]
) + '\n'
else:
del config_dict[key]
logger.debug('Using configuration: %s', configuration)
Expand Down
36 changes: 36 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,45 @@ def test_shutdown_race_condition(self):
db._cleanup()
del db
db = redislite.StrictRedis('bug.redis')
db._cleanup()
if os.path.exists('bug.redis'):
os.remove('bug.redis')

def test_redis_log_attribute(self):
r = redislite.StrictRedis()
self.assertIn('Server started, Redis version', r.redis_log)

def test_redis_log_tail(self):
r = redislite.StrictRedis()
lines = r.redis_log_tail(2)
self.assertIsInstance(lines, list)
self.assertEqual(len(lines), 2)

def test_redis_log_many_lines(self):
r = redislite.StrictRedis()
lines = r.redis_log_tail(lines=99999)
self.assertIsInstance(lines, list)

def test_redis_log_small_chunks(self):
r = redislite.StrictRedis()
lines = r.redis_log_tail(4, width=20)
self.assertIsInstance(lines, list)
self.assertEqual(len(lines), 4)

def test_redis_log_tail_no_log(self):
r = redislite.StrictRedis()
if os.path.exists(r.logfile):
os.remove(r.logfile)
lines = r.redis_log_tail()
self.assertEqual(lines, [])

def test_redis_log_tail_empty_log(self):
r = redislite.StrictRedis()
with open(r.logfile, 'w'):
pass
lines = r.redis_log_tail()
self.assertEqual(lines, [])


if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
Expand Down
Loading

0 comments on commit b01734a

Please sign in to comment.