# Path: django_docker_helpers/config/exceptions.py
# class KVStorageKeyDoestNotExist(KeyError):
# class KVStorageValueIsEmpty(ValueError):
# class RequiredValueIsEmpty(ValueError):
# 
# Path: django_docker_helpers/config/backends/redis_parser.py
# class RedisParser(BaseParser):
#     """
#     Reads a whole config bundle from a redis key and provides the unified interface to access config options.
# 
#     It assumes that config in your storage can be parsed with any simple parser, like
#     :class:`~django_docker_helpers.config.backends.YamlParser`.
# 
#     Compared to, e.g. :class:`~django_docker_helpers.config.backends.environment_parser.EnvironmentParser`
#     it does not have scope support by design, since ``endpoint`` is a good enough scope by itself.
# 
#     Example:
#     ::
# 
#         parser = RedisParser('my/server/config.yml', host=REDIS_HOST, port=REDIS_PORT)
#         parser.get('nested.a.b', coerce_type=int)
# 
#     """
#     def __init__(self,
#                  endpoint: str = 'service',
#                  host: str = '127.0.0.1',
#                  port: int = 6379,
#                  db: int = 0,
#                  path_separator: str = '.',
#                  inner_parser_class: t.Optional[t.Type[BaseParser]] = YamlParser,
#                  **redis_options):
#         """
# 
#         :param endpoint: specifies a redis key with serialized config, e.g. ``'services/mailer/config.yml'``
#         :param host: redis host, default is ``'127.0.0.1'``
#         :param port: redis port, default id ``6379``
#         :param db: redis database, default is ``0``
#         :param path_separator: specifies which character separates nested variables, default is ``'.'``
#         :param inner_parser_class: use the specified parser to read config from ``endpoint`` key
#         :param redis_options: additional options for ``redis.Redis`` client
#         """
# 
#         super().__init__(path_separator=path_separator)
#         self.inner_parser_class = inner_parser_class
#         self.endpoint = endpoint
# 
#         self.client_options = {
#             'host': host,
#             'port': port,
#             'db': db,
#         }
#         self.client_options.update(**redis_options)
#         self._inner_parser = None
# 
#     def __str__(self):
#         return '<{0} {1[host]}:{1[port]} db={1[db]} scope={2}>'.format(
#             self.__class__.__name__,
#             self.client_options,
#             self.scope,
#         )
# 
#     def get_client(self):
#         # type: () -> redis.Redis
#         import redis
#         self._client = redis.Redis(**self.client_options)
#         return self._client
# 
#     @property
#     def inner_parser(self) -> BaseParser:
#         """
#         Prepares inner config parser for config stored at ``endpoint``.
# 
#         :return: an instance of :class:`~django_docker_helpers.config.backends.base.BaseParser`
# 
#         :raises config.exceptions.KVStorageValueIsEmpty: if specified ``endpoint`` does not contain a config
#         """
#         if self._inner_parser is not None:
#             return self._inner_parser
# 
#         config = self.client.get(self.endpoint)
#         if not config:
#             raise KVStorageValueIsEmpty('Key `{0}` does not exist or value is empty'.format(self.endpoint))
# 
#         config = config.decode()
# 
#         self._inner_parser = self.inner_parser_class(
#             config=io.StringIO(config),
#             path_separator=self.path_separator,
#             scope=None
#         )
#         return self._inner_parser
# 
#     def get(self,
#             variable_path: str,
#             default: t.Optional[t.Any] = None,
#             coerce_type: t.Optional[t.Type] = None,
#             coercer: t.Optional[t.Callable] = None,
#             **kwargs):
# 
#         """
#         Reads a value of ``variable_path`` from redis storage.
# 
#         :param variable_path: a delimiter-separated path to a nested value
#         :param default: default value if there's no object by specified path
#         :param coerce_type: cast a type of a value to a specified one
#         :param coercer: perform a type casting with specified callback
#         :param kwargs: additional arguments inherited parser may need
#         :return: value or default
# 
#         :raises config.exceptions.KVStorageValueIsEmpty: if specified ``endpoint`` does not contain a config
#         """
# 
#         return self.inner_parser.get(
#             variable_path,
#             default=default,
#             coerce_type=coerce_type,
#             coercer=coercer,
#             **kwargs,
#         )


import pytest
import os
    import redis
from yaml import dump as yaml_dump
from django_docker_helpers.config import exceptions
from django_docker_helpers.config.backends.redis_parser import RedisParser

@pytest.fixture
def redis_parser():
    return RedisParser('my/server/config.yml', host=REDIS_HOST, port=REDIS_PORT)


@pytest.fixture
def store_redis_config():

    c = redis.Redis(host=REDIS_HOST, port=REDIS_PORT)
    data = yaml_dump(SAMPLE, allow_unicode=True).encode()
    c.set('my/server/config.yml', data)
    c.set('my/server/empty.yml', '')
    return c


# noinspection PyMethodMayBeStatic,PyShadowingNames,PyUnusedLocal
class RedisBackendTest:
    def test__redis_parser__client(self, redis_parser: RedisParser):
        assert redis_parser.client

    def test__redis_parser__inner_parser(self, store_redis_config, redis_parser: RedisParser):
        assert redis_parser.client
        assert redis_parser.inner_parser

    def test__redis_parser__inner_parser__exceptions(self, store_redis_config):
        with pytest.raises(exceptions.KVStorageValueIsEmpty):
            c = RedisParser('nothing/here', host=REDIS_HOST, port=REDIS_PORT)
            assert c.inner_parser


# Path: django_docker_helpers/config/exceptions.py
# class KVStorageKeyDoestNotExist(KeyError):
# class KVStorageValueIsEmpty(ValueError):
# class RequiredValueIsEmpty(ValueError):
# 
# Path: django_docker_helpers/config/backends/redis_parser.py
# class RedisParser(BaseParser):
#     """
#     Reads a whole config bundle from a redis key and provides the unified interface to access config options.
# 
#     It assumes that config in your storage can be parsed with any simple parser, like
#     :class:`~django_docker_helpers.config.backends.YamlParser`.
# 
#     Compared to, e.g. :class:`~django_docker_helpers.config.backends.environment_parser.EnvironmentParser`
#     it does not have scope support by design, since ``endpoint`` is a good enough scope by itself.
# 
#     Example:
#     ::
# 
#         parser = RedisParser('my/server/config.yml', host=REDIS_HOST, port=REDIS_PORT)
#         parser.get('nested.a.b', coerce_type=int)
# 
#     """
#     def __init__(self,
#                  endpoint: str = 'service',
#                  host: str = '127.0.0.1',
#                  port: int = 6379,
#                  db: int = 0,
#                  path_separator: str = '.',
#                  inner_parser_class: t.Optional[t.Type[BaseParser]] = YamlParser,
#                  **redis_options):
#         """
# 
#         :param endpoint: specifies a redis key with serialized config, e.g. ``'services/mailer/config.yml'``
#         :param host: redis host, default is ``'127.0.0.1'``
#         :param port: redis port, default id ``6379``
#         :param db: redis database, default is ``0``
#         :param path_separator: specifies which character separates nested variables, default is ``'.'``
#         :param inner_parser_class: use the specified parser to read config from ``endpoint`` key
#         :param redis_options: additional options for ``redis.Redis`` client
#         """
# 
#         super().__init__(path_separator=path_separator)
#         self.inner_parser_class = inner_parser_class
#         self.endpoint = endpoint
# 
#         self.client_options = {
#             'host': host,
#             'port': port,
#             'db': db,
#         }
#         self.client_options.update(**redis_options)
#         self._inner_parser = None
# 
#     def __str__(self):
#         return '<{0} {1[host]}:{1[port]} db={1[db]} scope={2}>'.format(
#             self.__class__.__name__,
#             self.client_options,
#             self.scope,
#         )
# 
#     def get_client(self):
#         # type: () -> redis.Redis
#         import redis
#         self._client = redis.Redis(**self.client_options)
#         return self._client
# 
#     @property
#     def inner_parser(self) -> BaseParser:
#         """
#         Prepares inner config parser for config stored at ``endpoint``.
# 
#         :return: an instance of :class:`~django_docker_helpers.config.backends.base.BaseParser`
# 
#         :raises config.exceptions.KVStorageValueIsEmpty: if specified ``endpoint`` does not contain a config
#         """
#         if self._inner_parser is not None:
#             return self._inner_parser
# 
#         config = self.client.get(self.endpoint)
#         if not config:
#             raise KVStorageValueIsEmpty('Key `{0}` does not exist or value is empty'.format(self.endpoint))
# 
#         config = config.decode()
# 
#         self._inner_parser = self.inner_parser_class(
#             config=io.StringIO(config),
#             path_separator=self.path_separator,
#             scope=None
#         )
#         return self._inner_parser
# 
#     def get(self,
#             variable_path: str,
#             default: t.Optional[t.Any] = None,
#             coerce_type: t.Optional[t.Type] = None,
#             coercer: t.Optional[t.Callable] = None,
#             **kwargs):
# 
#         """
#         Reads a value of ``variable_path`` from redis storage.
# 
#         :param variable_path: a delimiter-separated path to a nested value
#         :param default: default value if there's no object by specified path
#         :param coerce_type: cast a type of a value to a specified one
#         :param coercer: perform a type casting with specified callback
#         :param kwargs: additional arguments inherited parser may need
#         :return: value or default
# 
#         :raises config.exceptions.KVStorageValueIsEmpty: if specified ``endpoint`` does not contain a config
#         """
# 
#         return self.inner_parser.get(
#             variable_path,
#             default=default,
#             coerce_type=coerce_type,
#             coercer=coercer,
#             **kwargs,
#         )


# Path: tests/test_backend_redis.py
import pytest
import os
    import redis
from yaml import dump as yaml_dump
from django_docker_helpers.config import exceptions
from django_docker_helpers.config.backends.redis_parser import RedisParser


@pytest.fixture
def redis_parser():
    return RedisParser('my/server/config.yml', host=REDIS_HOST, port=REDIS_PORT)


@pytest.fixture
def store_redis_config():

    c = redis.Redis(host=REDIS_HOST, port=REDIS_PORT)
    data = yaml_dump(SAMPLE, allow_unicode=True).encode()
    c.set('my/server/config.yml', data)
    c.set('my/server/empty.yml', '')
    return c


# noinspection PyMethodMayBeStatic,PyShadowingNames,PyUnusedLocal
class RedisBackendTest:
    def test__redis_parser__client(self, redis_parser: RedisParser):
        assert redis_parser.client

    def test__redis_parser__inner_parser(self, store_redis_config, redis_parser: RedisParser):
        assert redis_parser.client
        assert redis_parser.inner_parser

    def test__redis_parser__inner_parser__exceptions(self, store_redis_config):
        with pytest.raises(exceptions.KVStorageValueIsEmpty):
            c = RedisParser('nothing/here', host=REDIS_HOST, port=REDIS_PORT)
            assert c.inner_parser


        with pytest.raises(exceptions.KVStorageValueIsEmpty):