settings.py 44.6 KB
Newer Older
1 2 3 4 5 6 7
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2016 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.

8
from contextlib import contextmanager
Matias Guijarro's avatar
Matias Guijarro committed
9 10
import weakref
import pickle
11 12
import keyword
import re
13
import reprlib
14

15
import numpy
Matias Guijarro's avatar
Matias Guijarro committed
16

17 18 19
from .conductor import client
from bliss.common.utils import Null
from bliss import setup_globals
20 21
from tabulate import tabulate

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
22

Matias Guijarro's avatar
Matias Guijarro committed
23 24 25
class InvalidValue(Null):
    def __str__(self):
        raise ValueError
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
26

Matias Guijarro's avatar
Matias Guijarro committed
27
    def __repr__(self):
28
        return "#ERR"
29

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
30

31
class DefaultValue:
32 33 34 35 36 37
    def __init__(self, wrapped_value):
        self.__value = wrapped_value

    @property
    def value(self):
        return self.__value
Matias Guijarro's avatar
Matias Guijarro committed
38

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
39 40

def boolify(s, **keys):
41
    if s in ("True", "true"):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
42
        return True
43
    if s in ("False", "false"):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
44
        return False
45
    raise ValueError("Not Boolean Value!")
Matias Guijarro's avatar
Matias Guijarro committed
46

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
47

48 49 50
def auto_coerce_from_redis(s):
    """Convert variable to a new type from the str representation"""
    if s is None:
Matias Guijarro's avatar
Matias Guijarro committed
51
        return None
52 53 54 55 56 57 58 59
    # Default is unicode string
    try:
        if isinstance(s, bytes):
            s = s.decode()
    # Pickled data fails at first byte
    except UnicodeDecodeError:
        pass
    # Cast to standard types
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
60
    for caster in (boolify, int, float):
Matias Guijarro's avatar
Matias Guijarro committed
61
        try:
62
            return caster(s)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
63
        except (ValueError, TypeError):
Matias Guijarro's avatar
Matias Guijarro committed
64
            pass
65
    return s
Matias Guijarro's avatar
Matias Guijarro committed
66

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
67

68
def pickle_loads(var):
Matias Guijarro's avatar
Matias Guijarro committed
69 70
    if var is None:
        return None
Matias Guijarro's avatar
Matias Guijarro committed
71 72 73 74
    try:
        return pickle.loads(var)
    except Exception:
        return InvalidValue()
Matias Guijarro's avatar
Matias Guijarro committed
75

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
76

77 78
def get_redis_connection():
    return client.get_redis_connection(db=0)
79 80


Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
81
def ttl_func(cnx, name, value=-1):
Matias Guijarro's avatar
Matias Guijarro committed
82 83 84 85 86
    if value is None:
        return cnx.persist(name)
    elif value is -1:
        return cnx.ttl(name)
    else:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
87 88
        return cnx.expire(name, value)

Matias Guijarro's avatar
Matias Guijarro committed
89

90
def read_decorator(func):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
91 92
    def _read(self, *args, **keys):
        value = func(self, *args, **keys)
Matias Guijarro's avatar
Matias Guijarro committed
93
        if self._read_type_conversion:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
94
            if isinstance(value, list):
Matias Guijarro's avatar
Matias Guijarro committed
95
                value = [self._read_type_conversion(x) for x in value]
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
96
            elif isinstance(value, dict):
Vincent Michel's avatar
Vincent Michel committed
97
                for k, v in value.items():
Matias Guijarro's avatar
Matias Guijarro committed
98
                    value[k] = self._read_type_conversion(v)
99 100 101
                if hasattr(self, "default_values") and isinstance(
                    self.default_values, dict
                ):
102 103 104
                    tmp = dict(self._default_values)
                    tmp.update(value)
                    value = tmp
Matias Guijarro's avatar
Matias Guijarro committed
105
            else:
106 107 108
                if isinstance(value, DefaultValue):
                    value = value.value
                elif value is not None:
109
                    value = self._read_type_conversion(value)
110
        if value is None:
111
            if hasattr(self, "_default_value"):
112
                value = self._default_value
113 114 115
            elif hasattr(self, "_default_values") and hasattr(
                self._default_values, "get"
            ):
116
                value = self._default_values.get(args[0])
Matias Guijarro's avatar
Matias Guijarro committed
117
        return value
118

Matias Guijarro's avatar
Matias Guijarro committed
119 120
    return _read

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
121

122
def write_decorator_dict(func):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
123
    def _write(self, values, **keys):
Matias Guijarro's avatar
Matias Guijarro committed
124
        if self._write_type_conversion:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
125
            if not isinstance(values, dict) and values is not None:
126
                raise TypeError("can only be dict")
Matias Guijarro's avatar
Matias Guijarro committed
127 128

            if values is not None:
129
                new_dict = dict()
Vincent Michel's avatar
Vincent Michel committed
130
                for k, v in values.items():
131 132
                    new_dict[k] = self._write_type_conversion(v)
                values = new_dict
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
133
        return func(self, values, **keys)
134

Matias Guijarro's avatar
Matias Guijarro committed
135 136
    return _write

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
137

138
def write_decorator_multiple(func):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
139
    def _write(self, values, **keys):
Matias Guijarro's avatar
Matias Guijarro committed
140
        if self._write_type_conversion:
141 142 143 144 145
            if (
                not isinstance(values, (list, tuple, numpy.ndarray))
                and values is not None
            ):
                raise TypeError("Can only be tuple, list or numpy array")
Matias Guijarro's avatar
Matias Guijarro committed
146 147
            if values is not None:
                values = [self._write_type_conversion(x) for x in values]
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
148
        return func(self, values, **keys)
149

Matias Guijarro's avatar
Matias Guijarro committed
150 151
    return _write

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
152

153
def write_decorator(func):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
154
    def _write(self, value, **keys):
Matias Guijarro's avatar
Matias Guijarro committed
155 156
        if self._write_type_conversion and value is not None:
            value = self._write_type_conversion(value)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
157
        return func(self, value, **keys)
158

Matias Guijarro's avatar
Matias Guijarro committed
159 160
    return _write

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
161

162
def scan(match="*", count=1000, connection=None):
163
    if connection is None:
164
        connection = get_redis_connection()
165 166
    cursor = 0
    while 1:
167
        cursor, values = connection.scan(cursor=cursor, match=match, count=count)
168
        for val in values:
169
            yield val.decode()
170 171
        if int(cursor) == 0:
            break
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
172 173


174 175 176 177 178 179
def _get_connection(setting_object):
    """
    Return the connection of a setting_object
    """
    if isinstance(setting_object, Struct):
        return setting_object._proxy._cnx
180
    elif isinstance(setting_object, (SimpleSetting, QueueSetting, BaseHashSetting)):
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
        return setting_object._cnx
    else:
        raise TypeError(
            f"Setting object should be one of: Struct, SimpleSetting, QueueSetting or HashSetting instead of {setting_object!r}"
        )


def _set_connection(setting_object, new_cnx):
    """
    change the connection of a setting_object
    and return the previous connection
    """
    if isinstance(setting_object, Struct):
        cnx = setting_object._proxy._cnx
        setting_object._proxy._cnx = new_cnx
196
    elif isinstance(setting_object, (SimpleSetting, QueueSetting, BaseHashSetting)):
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
        cnx = setting_object._cnx
        setting_object._cnx = new_cnx
    else:
        raise TypeError(
            f"Setting object should be one of: Struct, SimpleSetting, QueueSetting or HashSetting instead of {setting_object!r}"
        )
    return cnx


@contextmanager
def pipeline(*settings):
    """
    Contextmanager which create a redis pipeline to group redis commands
    on settings.

    IN CASE OF you execute the pipeline, it will return raw database values
    (byte strings).
    """
    first_settings = settings[0]
    cnx = _get_connection(first_settings)()
    # check they have the same connection
    for s in settings[1:]:
        if _get_connection(s)() != cnx:
            raise RuntimeError("Cannot groupe redis commands in a pipeline")

    pipeline = cnx.pipeline()
    try:
        # replace settings connection with the pipeline
        previous_cnx = [_set_connection(s, weakref.ref(pipeline)) for s in settings]
        yield pipeline
    finally:
        [_set_connection(s, c) for c, s in zip(previous_cnx, settings)]
        pipeline.execute()


232 233
class SimpleSetting:
    """
234
    Class to manage a setting that is stored as a string on Redis
235
    """
236

237 238 239 240
    def __init__(
        self,
        name,
        connection=None,
241 242
        read_type_conversion=auto_coerce_from_redis,
        write_type_conversion=str,
243 244
        default_value=None,
    ):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
245
        if connection is None:
246
            connection = get_redis_connection()
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
247 248 249 250
        self._cnx = weakref.ref(connection)
        self._name = name
        self._read_type_conversion = read_type_conversion
        self._write_type_conversion = write_type_conversion
251
        self._default_value = default_value
Matias Guijarro's avatar
Matias Guijarro committed
252

253 254 255
    @property
    def name(self):
        return self._name
256

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
257
    @read_decorator
258
    def get(self):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
259 260 261 262 263
        cnx = self._cnx()
        value = cnx.get(self._name)
        return value

    @write_decorator
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
264
    def set(self, value):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
265
        cnx = self._cnx()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
266 267 268 269
        cnx.set(self._name, value)

    def ttl(self, value=-1):
        return ttl_func(self._cnx(), self._name, value)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
270

271 272 273
    def clear(self):
        cnx = self._cnx()
        cnx.delete(self._name)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
274 275

    def __add__(self, other):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
276
        value = self.get()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
277
        if isinstance(other, SimpleSetting):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
278 279 280
            other = other.get()
        return value + other

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
281
    def __iadd__(self, other):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
282 283
        cnx = self._cnx()
        if cnx is not None:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
284
            if isinstance(other, int):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
285 286
                if other == 1:
                    cnx.incr(self._name)
Matias Guijarro's avatar
Matias Guijarro committed
287
                else:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
288 289 290
                    cnx.incrby(self._name, other)
            elif isinstance(other, float):
                cnx.incrbyfloat(self._name, other)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
291
            else:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
292
                cnx.append(self._name, other)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
293
            return self
Matias Guijarro's avatar
Matias Guijarro committed
294

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
295
    def __isub__(self, other):
Vincent Michel's avatar
Vincent Michel committed
296
        if isinstance(other, str):
297 298 299
            raise TypeError(
                "unsupported operand type(s) for -=: %s" % type(other).__name__
            )
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
300
        return self.__iadd__(-other)
Matias Guijarro's avatar
Matias Guijarro committed
301

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
302
    def __getitem__(self, ran):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
303 304 305
        cnx = self._cnx()
        if cnx is not None:
            step = None
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
306 307
            if isinstance(ran, slice):
                i, j = ran.start, ran.stop
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
308
                step = ran.step
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
309
            elif isinstance(ran, int):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
310 311
                i = j = ran
            else:
312
                raise TypeError("indices must be integers")
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
313

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
314
            value = cnx.getrange(self._name, i, j)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
315 316 317 318 319 320 321
            if step is not None:
                value = value[0:-1:step]
            return value

    def __repr__(self):
        cnx = self._cnx()
        value = cnx.get(self._name)
322
        return "<SimpleSetting name=%s value=%s>" % (self._name, value)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
323

Matias Guijarro's avatar
Matias Guijarro committed
324

325 326 327 328 329 330
class SimpleSettingProp:
    """
    A python's property implementation for SimpleSetting
    To be used inside user defined classes
    """

331 332 333 334
    def __init__(
        self,
        name,
        connection=None,
335 336
        read_type_conversion=auto_coerce_from_redis,
        write_type_conversion=str,
337 338 339
        default_value=None,
        use_object_name=True,
    ):
Matias Guijarro's avatar
Matias Guijarro committed
340
        self._name = name
341
        self._cnx = connection or get_redis_connection()
Matias Guijarro's avatar
Matias Guijarro committed
342 343
        self._read_type_conversion = read_type_conversion
        self._write_type_conversion = write_type_conversion
344
        self._default_value = default_value
Matias Guijarro's avatar
Matias Guijarro committed
345 346
        self._use_object_name = use_object_name

347 348 349 350
    @property
    def name(self):
        return self._name

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
351
    def __get__(self, obj, type=None):
Matias Guijarro's avatar
Matias Guijarro committed
352
        if self._use_object_name:
353
            name = obj.name + ":" + self._name
Matias Guijarro's avatar
Matias Guijarro committed
354 355
        else:
            name = self._name
356 357 358 359 360 361 362
        return SimpleSetting(
            name,
            self._cnx,
            self._read_type_conversion,
            self._write_type_conversion,
            self._default_value,
        )
Matias Guijarro's avatar
Matias Guijarro committed
363

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
364 365 366
    def __set__(self, obj, value):
        if isinstance(value, SimpleSetting):
            return
Matias Guijarro's avatar
Matias Guijarro committed
367 368

        if self._use_object_name:
369
            name = obj.name + ":" + self._name
Matias Guijarro's avatar
Matias Guijarro committed
370 371 372 373 374 375 376 377
        else:
            name = self._name

        if value is None:
            self._cnx.delete(name)
        else:
            if self._write_type_conversion:
                value = self._write_type_conversion(value)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
378 379
            self._cnx.set(name, value)

Matias Guijarro's avatar
Matias Guijarro committed
380

381 382
class QueueSetting:
    """
383
    Class to manage a setting that is stored as a list on Redis
384 385
    """

386 387 388 389
    def __init__(
        self,
        name,
        connection=None,
390 391
        read_type_conversion=auto_coerce_from_redis,
        write_type_conversion=str,
392
    ):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
393
        if connection is None:
394
            connection = get_redis_connection()
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
395 396 397 398
        self._cnx = weakref.ref(connection)
        self._name = name
        self._read_type_conversion = read_type_conversion
        self._write_type_conversion = write_type_conversion
Matias Guijarro's avatar
Matias Guijarro committed
399

400 401 402 403
    @property
    def name(self):
        return self._name

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
404
    @read_decorator
405 406 407
    def get(self, first=0, last=-1, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
408
        if first == last:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
409
            l = cnx.lindex(self._name, first)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
410
        else:
411 412
            if last != -1:
                last -= 1
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
413
            l = cnx.lrange(self._name, first, last)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
414
        return l
Matias Guijarro's avatar
Matias Guijarro committed
415

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
416
    @write_decorator
417 418 419
    def append(self, value, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
420
        return cnx.rpush(self._name, value)
Matias Guijarro's avatar
Matias Guijarro committed
421

422 423 424
    def clear(self, cnx=None):
        if cnx is None:
            cnx = self._cnx()
425 426
        cnx.delete(self._name)

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
427
    @write_decorator
428 429 430
    def prepend(self, value, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
431
        return cnx.lpush(self._name, value)
Matias Guijarro's avatar
Matias Guijarro committed
432

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
433
    @write_decorator_multiple
434
    def extend(self, values, cnx=None):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
435
        cnx = self._cnx()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
436
        return cnx.rpush(self._name, *values)
Matias Guijarro's avatar
Matias Guijarro committed
437

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
438
    @write_decorator
439 440 441
    def remove(self, value, cnx=None):
        if cnx is None:
            cnx = self._cnx()
442
        cnx.lrem(self._name, 0, value)
Matias Guijarro's avatar
Matias Guijarro committed
443

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
444
    @write_decorator_multiple
445 446 447
    def set(self, values, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
448 449
        cnx.delete(self._name)
        if values is not None:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
450
            cnx.rpush(self._name, *values)
Matias Guijarro's avatar
Matias Guijarro committed
451

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
452
    @write_decorator
453 454 455
    def set_item(self, value, pos=0, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
456
        cnx.lset(self._name, pos, value)
Matias Guijarro's avatar
Matias Guijarro committed
457

Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
458
    @read_decorator
459 460 461
    def pop_front(self, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
462 463 464 465 466 467
        value = cnx.lpop(self._name)
        if self._read_type_conversion:
            value = self._read_type_conversion(value)
        return value

    @read_decorator
468 469 470
    def pop_back(self, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
471 472 473 474 475
        value = cnx.rpop(self._name)
        if self._read_type_conversion:
            value = self._read_type_conversion(value)
        return value

476 477 478 479
    def ttl(self, value=-1, cnx=None):
        if cnx is None:
            cnx = self._cnx()
        return ttl_func(cnx, self._name, value)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
480

481 482 483
    def __len__(self, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
484 485
        return cnx.llen(self._name)

486 487 488
    def __repr__(self, cnx=None):
        if cnx is None:
            cnx = self._cnx()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
489
        value = cnx.lrange(self._name, 0, -1)
490
        return "<QueueSetting name=%s value=%s>" % (self._name, value)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
491

492 493
    def __iadd__(self, other, cnx=None):
        self.extend(other, cnx)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
494 495
        return self

496
    def __getitem__(self, ran, cnx=None):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
497
        if isinstance(ran, slice):
498 499
            i = ran.start is not None and ran.start or 0
            j = ran.stop is not None and ran.stop or -1
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
500
        elif isinstance(ran, int):
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
501 502
            i = j = ran
        else:
503
            raise TypeError("indices must be integers")
504
        value = self.get(first=i, last=j, cnx=cnx)
505
        if value is None:
506
            raise IndexError
507 508
        else:
            return value
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
509

510
    def __iter__(self, cnx=None):
511 512
        if cnx is None:
            cnx = self._cnx()
513
        lsize = cnx.llen(self._name)
Vincent Michel's avatar
Vincent Michel committed
514
        for first in range(0, lsize, 1024):
515
            last = first + 1024
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
516 517 518
            if last >= lsize:
                last = -1
            for value in self.get(first, last):
519
                yield value
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
520

521
    def __setitem__(self, ran, value, cnx=None):
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
522
        if isinstance(ran, slice):
523
            for i, v in zip(range(ran.start, ran.stop), value):
524
                self.set_item(v, pos=i, cnx=cnx)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
525
        elif isinstance(ran, int):
526
            self.set_item(value, pos=ran, cnx=cnx)
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
527
        else:
528
            raise TypeError("indices must be integers")
Sebastien Petitdemange's avatar
Sebastien Petitdemange committed
529 530
        return self

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
531

532 533 534 535 536 537
class QueueSettingProp:
    """
    A python's property implementation for QueueSetting
    To be used inside user defined classes
    """

538 539 540 541
    def __init__(
        self,
        name,
        connection=None,
542 543
        read_type_conversion=auto_coerce_from_redis,
        write_type_conversion=str,
544 545
        use_object_name=True,
    ):
Matias Guijarro's avatar
Matias Guijarro committed
546
        self._name = name
547
        self._cnx = connection or get_redis_connection()
Matias Guijarro's avatar
Matias Guijarro committed
548 549 550 551
        self._read_type_conversion = read_type_conversion
        self._write_type_conversion = write_type_conversion
        self._use_object_name = use_object_name

552 553 554 555
    @property
    def name(self):
        return self._name

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
556
    def __get__(self, obj, type=None):
Matias Guijarro's avatar
Matias Guijarro committed
557
        if self._use_object_name:
558
            name = obj.name + ":" + self._name
Matias Guijarro's avatar
Matias Guijarro committed
559 560 561
        else:
            name = self._name

562 563 564
        return QueueSetting(
            name, self._cnx, self._read_type_conversion, self._write_type_conversion
        )
Matias Guijarro's avatar
Matias Guijarro committed
565

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
566 567 568
    def __set__(self, obj, values):
        if isinstance(values, QueueSetting):
            return
Matias Guijarro's avatar
Matias Guijarro committed
569 570

        if self._use_object_name:
571
            name = obj.name + ":" + self._name
Matias Guijarro's avatar
Matias Guijarro committed
572 573 574
        else:
            name = self._name

575 576 577
        proxy = QueueSetting(
            name, self._cnx, self._read_type_conversion, self._write_type_conversion
        )
Matias Guijarro's avatar
Matias Guijarro committed
578 579
        proxy.set(values)

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
580

581 582 583 584 585 586 587 588 589 590 591 592 593
class BaseHashSetting:
    """
    A Setting stored as a key,value pair in Redis

    Args:
        name: name of the BaseHashSetting (used on Redis)
        connection: Redis connection object
            read_type_conversion: conversion of data applyed
                after reading
            write_type_conversion: conversion of data applyed
                before writing
    """

594 595 596 597
    def __init__(
        self,
        name,
        connection=None,
598 599
        read_type_conversion=auto_coerce_from_redis,
        write_type_conversion=str,
600
    ):
Matias Guijarro's avatar
Matias Guijarro committed
601
        if connection is None:
602
            connection = get_redis_connection()
Matias Guijarro's avatar
Matias Guijarro committed
603 604 605 606 607
        self._cnx = weakref.ref(connection)
        self._name = name
        self._read_type_conversion = read_type_conversion
        self._write_type_conversion = write_type_conversion

608 609 610 611
    @property
    def name(self):
        return self._name

612 613
    def __repr__(self):
        value = self.get_all()
614
        return f"<{type(self).__name__} name=%s value=%s>" % (self._name, value)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
615 616

    def __delitem__(self, key):
617
        cnx = self._cnx()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
618
        cnx.hdel(self._name, key)
Matias Guijarro's avatar
Matias Guijarro committed
619

620
    def __len__(self):
Matias Guijarro's avatar
Matias Guijarro committed
621 622 623
        cnx = self._cnx()
        return cnx.hlen(self._name)

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
624 625
    def ttl(self, value=-1):
        return ttl_func(self._cnx(), self._name, value)
Matias Guijarro's avatar
Matias Guijarro committed
626

627
    def raw_get(self, *keys):
Matias Guijarro's avatar
Matias Guijarro committed
628
        cnx = self._cnx()
629
        return cnx.hget(self._name, *keys)
Matias Guijarro's avatar
Matias Guijarro committed
630 631

    @read_decorator
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
632
    def get(self, key, default=None):
Matias Guijarro's avatar
Matias Guijarro committed
633 634
        v = self.raw_get(key)
        if v is None:
635
            v = DefaultValue(default)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
636
        return v
Matias Guijarro's avatar
Matias Guijarro committed
637

Matias Guijarro's avatar
Matias Guijarro committed
638 639 640
    def _raw_get_all(self):
        cnx = self._cnx()
        return cnx.hgetall(self._name)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
641

Matias Guijarro's avatar
Matias Guijarro committed
642
    def get_all(self):
643
        all_dict = dict()
Vincent Michel's avatar
Vincent Michel committed
644
        for k, raw_v in self._raw_get_all().items():
645
            k = k.decode()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
646 647 648
            v = self._read_type_conversion(raw_v)
            if isinstance(v, InvalidValue):
                raise ValueError(
649 650 651
                    "%s: Invalid value '%s` (cannot deserialize %r)"
                    % (self._name, k, raw_v)
                )
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
652
            all_dict[k] = v
Matias Guijarro's avatar
Matias Guijarro committed
653
        return all_dict
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
654

Matias Guijarro's avatar
Matias Guijarro committed
655
    @read_decorator
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
656
    def pop(self, key, default=Null()):
657
        cnx = self._cnx().pipeline()
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
658 659 660
        cnx.hget(self._name, key)
        cnx.hdel(self._name, key)
        (value, worked) = cnx.execute()
661
        if not worked:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
662
            if isinstance(default, Null):
663 664 665
                raise KeyError(key)
            else:
                value = default
Matias Guijarro's avatar
Matias Guijarro committed
666
        return value
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
667

Matias Guijarro's avatar
Matias Guijarro committed
668
    def remove(self, *keys):
Matias Guijarro's avatar
Matias Guijarro committed
669
        cnx = self._cnx()
Matias Guijarro's avatar
Matias Guijarro committed
670
        cnx.hdel(self._name, *keys)
Matias Guijarro's avatar
Matias Guijarro committed
671

672
    def clear(self):
Matias Guijarro's avatar
Matias Guijarro committed
673 674 675
        cnx = self._cnx()
        cnx.delete(self._name)

676
    def copy(self):
Matias Guijarro's avatar
Matias Guijarro committed
677 678 679
        return self.get()

    @write_decorator_dict
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
680
    def set(self, values):
Matias Guijarro's avatar
Matias Guijarro committed
681 682 683
        cnx = self._cnx()
        cnx.delete(self._name)
        if values is not None:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
684
            cnx.hmset(self._name, values)
Matias Guijarro's avatar
Matias Guijarro committed
685 686

    @write_decorator_dict
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
687
    def update(self, values):
Matias Guijarro's avatar
Matias Guijarro committed
688
        cnx = self._cnx()
689
        if values:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
690
            cnx.hmset(self._name, values)
Matias Guijarro's avatar
Matias Guijarro committed
691 692

    @read_decorator
693
    def fromkeys(self, *keys):
Matias Guijarro's avatar
Matias Guijarro committed
694
        cnx = self._cnx()
695
        return cnx.hmget(self._name, *keys)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
696 697

    def has_key(self, key):
Matias Guijarro's avatar
Matias Guijarro committed
698
        cnx = self._cnx()
699
        return cnx.hexists(self._name, key)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
700

701
    def keys(self):
Vincent Michel's avatar
Vincent Michel committed
702
        for k, v in self.items():
703 704
            yield k

705
    def values(self):
Vincent Michel's avatar
Vincent Michel committed
706
        for k, v in self.items():
707
            yield v
Matias Guijarro's avatar
Matias Guijarro committed
708

709
    def items(self):
Matias Guijarro's avatar
Matias Guijarro committed
710 711
        cnx = self._cnx()
        next_id = 0
712
        seen_keys = set()
Matias Guijarro's avatar
Matias Guijarro committed
713
        while True:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
714
            next_id, pd = cnx.hscan(self._name, next_id)
Vincent Michel's avatar
Vincent Michel committed
715
            for k, v in pd.items():
716 717
                # Add key conversion
                k = k.decode()
Matias Guijarro's avatar
Matias Guijarro committed
718 719
                if self._read_type_conversion:
                    v = self._read_type_conversion(v)
720
                seen_keys.add(k)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
721
                yield k, v
722
            if not next_id or next_id is "0":
Matias Guijarro's avatar
Matias Guijarro committed
723 724
                break

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
725
    def __getitem__(self, key):
Matias Guijarro's avatar
Matias Guijarro committed
726 727
        value = self.get(key)
        if value is None:
728
            raise KeyError(key)
Matias Guijarro's avatar
Matias Guijarro committed
729 730
        return value

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
731
    def __setitem__(self, key, value):
Matias Guijarro's avatar
Matias Guijarro committed
732
        cnx = self._cnx()
733
        if value is None:
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
734
            cnx.hdel(self._name, key)
735
            return
Matias Guijarro's avatar
Matias Guijarro committed
736 737
        if self._write_type_conversion:
            value = self._write_type_conversion(value)
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
738
        cnx.hset(self._name, key, value)
Matias Guijarro's avatar
Matias Guijarro committed
739

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
740
    def __contains__(self, key):
741 742 743 744 745 746 747
        try:
            self[key]
            return True
        except KeyError:
            return False


748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
class HashSetting(BaseHashSetting):
    """
    A Setting stored as a key,value pair in Redis
    with a default_value dictionary to serve as a callback
    when elements lookup fails

    Args:
        name: name of the HashSetting (used on Redis)
        connection: Redis connection object
            read_type_conversion: conversion of data applyed
                after reading
            write_type_conversion: conversion of data applyed
                before writing
    kwargs:
        default_values: dictionary of default values retrieved
            on fallback
    """

    def __init__(
        self,
        name,
        connection=None,
        read_type_conversion=auto_coerce_from_redis,
        write_type_conversion=str,
        default_values={},
    ):
        super().__init__(
            name,
            connection=connection,
            read_type_conversion=read_type_conversion,
            write_type_conversion=write_type_conversion,
        )
        self._default_values = default_values

    @read_decorator
    def get(self, key, default=None):
        v = super().raw_get(key)
        if v is None:
            v = DefaultValue(default)
        return v

    def has_key(self, key):
        return super().has_key(key) or key in self._default_values

    def __getitem__(self, key):
        value = self.get(key)
        if value is None:
            if key not in self._default_values:
                raise KeyError(key)
        return value

    def get_all(self):
        all_dict = dict(self._default_values)
        for k, raw_v in self._raw_get_all().items():
            k = k.decode()
            v = self._read_type_conversion(raw_v)
            if isinstance(v, InvalidValue):
                raise ValueError(
                    "%s: Invalid value '%s` (cannot deserialize %r)"
                    % (self._name, k, raw_v)
                )
            all_dict[k] = v
        return all_dict

    def items(self):
        cnx = self._cnx()
        next_id = 0
        seen_keys = set()
        while True:
            next_id, pd = cnx.hscan(self._name, next_id)
            for k, v in pd.items():
                # Add key conversion
                k = k.decode()
                if self._read_type_conversion:
                    v = self._read_type_conversion(v)
                seen_keys.add(k)
                yield k, v
            if not next_id or next_id is "0":
                break

        for k, v in self._default_values.items():
            if k in seen_keys:
                continue
            yield k, v


class HashSettingProp:
835 836 837 838
    def __init__(
        self,
        name,
        connection=None,
839 840
        read_type_conversion=auto_coerce_from_redis,
        write_type_conversion=str,
841 842 843
        default_values={},
        use_object_name=True,
    ):
Matias Guijarro's avatar
Matias Guijarro committed
844
        self._name = name
845
        self._cnx = connection or get_redis_connection()
Matias Guijarro's avatar
Matias Guijarro committed
846 847
        self._read_type_conversion = read_type_conversion
        self._write_type_conversion = write_type_conversion
848
        self._default_values = default_values
Matias Guijarro's avatar
Matias Guijarro committed
849 850
        self._use_object_name = use_object_name

851 852 853 854
    @property
    def name(self):
        return self._name

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
855
    def __get__(self, obj, type=None):
Matias Guijarro's avatar
Matias Guijarro committed
856
        if self._use_object_name:
857
            name = obj.name + ":" + self._name
Matias Guijarro's avatar
Matias Guijarro committed
858 859
        else:
            name = self._name
Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
860

861 862 863 864 865 866 867
        return HashSetting(
            name,
            self._cnx,
            self._read_type_conversion,
            self._write_type_conversion,
            self._default_values,
        )
Matias Guijarro's avatar
Matias Guijarro committed
868

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
869
    def __set__(self, obj, values):
Matias Guijarro's avatar
Matias Guijarro committed
870
        if self._use_object_name:
871
            name = obj.name + ":" + self._name
Matias Guijarro's avatar
Matias Guijarro committed
872 873 874
        else:
            name = self._name

Sebastien Petitdemange's avatar
pep8  
Sebastien Petitdemange committed
875 876
        if isinstance(values, HashSetting):
            return
Matias Guijarro's avatar
Matias Guijarro committed
877

878 879 880 881