utils.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import json
  2. import redis
  3. import uuid
  4. from urllib.parse import urlparse
  5. def parse_redis_sentinel_url(redis_url):
  6. parsed_url = urlparse(redis_url)
  7. if parsed_url.scheme != "redis":
  8. raise ValueError("Invalid Redis URL scheme. Must be 'redis'.")
  9. return {
  10. "username": parsed_url.username or None,
  11. "password": parsed_url.password or None,
  12. "service": parsed_url.hostname or 'mymaster',
  13. "port": parsed_url.port or 6379,
  14. "db": int(parsed_url.path.lstrip("/") or 0),
  15. }
  16. def get_redis_connection(redis_url, sentinels, decode_responses=True):
  17. """
  18. Creates a Redis connection from either a standard Redis URL or uses special
  19. parsing to setup a Sentinel connection, if given an array of host/port tuples.
  20. """
  21. if sentinels:
  22. redis_config = parse_redis_sentinel_url(redis_url)
  23. sentinel = redis.sentinel.Sentinel(
  24. self.sentinels,
  25. port=redis_config['port'],
  26. db=redis_config['db'],
  27. username=redis_config['username'],
  28. password=redis_config['password'],
  29. decode_responses=decode_responses
  30. }
  31. # Get a master connection from Sentinel
  32. return sentinel.master_for(redis_config['service'])
  33. else:
  34. # Standard Redis connection
  35. return redis.Redis.from_url(redis_url, decode_responses=decode_responses)
  36. class RedisLock:
  37. def __init__(self, redis_url, lock_name, timeout_secs, sentinels=[]):
  38. self.lock_name = lock_name
  39. self.lock_id = str(uuid.uuid4())
  40. self.timeout_secs = timeout_secs
  41. self.lock_obtained = False
  42. self.redis = get_redis_connection(redis_url, sentinels, decode_responses=True)
  43. def aquire_lock(self):
  44. # nx=True will only set this key if it _hasn't_ already been set
  45. self.lock_obtained = self.redis.set(
  46. self.lock_name, self.lock_id, nx=True, ex=self.timeout_secs
  47. )
  48. return self.lock_obtained
  49. def renew_lock(self):
  50. # xx=True will only set this key if it _has_ already been set
  51. return self.redis.set(
  52. self.lock_name, self.lock_id, xx=True, ex=self.timeout_secs
  53. )
  54. def release_lock(self):
  55. lock_value = self.redis.get(self.lock_name)
  56. if lock_value and lock_value == self.lock_id:
  57. self.redis.delete(self.lock_name)
  58. class RedisDict:
  59. def __init__(self, name, redis_url, sentinels=[]):
  60. self.name = name
  61. self.redis = get_redis_connection(redis_url, sentinels, decode_responses=True)
  62. def __setitem__(self, key, value):
  63. serialized_value = json.dumps(value)
  64. self.redis.hset(self.name, key, serialized_value)
  65. def __getitem__(self, key):
  66. value = self.redis.hget(self.name, key)
  67. if value is None:
  68. raise KeyError(key)
  69. return json.loads(value)
  70. def __delitem__(self, key):
  71. result = self.redis.hdel(self.name, key)
  72. if result == 0:
  73. raise KeyError(key)
  74. def __contains__(self, key):
  75. return self.redis.hexists(self.name, key)
  76. def __len__(self):
  77. return self.redis.hlen(self.name)
  78. def keys(self):
  79. return self.redis.hkeys(self.name)
  80. def values(self):
  81. return [json.loads(v) for v in self.redis.hvals(self.name)]
  82. def items(self):
  83. return [(k, json.loads(v)) for k, v in self.redis.hgetall(self.name).items()]
  84. def get(self, key, default=None):
  85. try:
  86. return self[key]
  87. except KeyError:
  88. return default
  89. def clear(self):
  90. self.redis.delete(self.name)
  91. def update(self, other=None, **kwargs):
  92. if other is not None:
  93. for k, v in other.items() if hasattr(other, "items") else other:
  94. self[k] = v
  95. for k, v in kwargs.items():
  96. self[k] = v
  97. def setdefault(self, key, default=None):
  98. if key not in self:
  99. self[key] = default
  100. return self[key]