We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[bug] Only one CombinedValidator is registered - subsequent are silently ignored
**Describe the bug**
If validators are added through `settings.validators.register()`, only first CombinedValidator is registered - subsequent are silently skipped.

**Analysis**
The root cause is `Validator.__eq__()` method. `ValidatorList.register()` will go through provided validators and add them, but only if they aren't already on the list (`validator not in self`). `in` will return "`True` if an item of *s* is equal to *x*" ([docs](https://docs.python.org/3.8/library/stdtypes.html#common-sequence-operations)). That logic was added in #256 .

When `Validator.__eq__()` compares different objects, it looks into various `Validator` properties and compares them in pair. If they are all the same, `__eq__()` will assume these are two instances of effectively the same validation rule.

The problem is, `CombinedValidator` doesn't have any of these properties, so two completely different `CombinedValidator` will appear to be the same for `__eq__()` method.


**To Reproduce**
In python shell:
```
>>> from dynaconf import Validator
>>> (Validator("foo.foo") | Validator("foo.bar")) == (Validator("bar.foo") & Validator("bar.bar"))
True
```

This should return False, as these two `CombinedValidator`s have nothing in common.

**Environment (please complete the following information):**
 - OS: Linux/Fedora32
 - Dynaconf master (6c568d687e29ca5ed9806a74f1f4fb7e4b96be2f), 3.0.1

**Additional context**
I might try working on patch, but I'm not sure about best approach. Perhaps we need type comparison inside `__eq__()` (so comparing AndValidator to OrValidator can return early). But how do we compare two `AndValidator`s? Look into combined validators properties recursively?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `dynaconf/validator.py`
Content:
```
1 from itertools import chain
2 from types import MappingProxyType
3 
4 from dynaconf import validator_conditions  # noqa
5 from dynaconf.utils.functional import empty
6 
7 
8 class ValidationError(Exception):
9     pass
10 
11 
12 class Validator(object):
13     """Validators are conditions attached to settings variables names
14     or patterns::
15 
16         Validator('MESSAGE', must_exist=True, eq='Hello World')
17 
18     The above ensure MESSAGE is available in default env and
19     is equal to 'Hello World'
20 
21     `names` are a one (or more) names or patterns::
22 
23         Validator('NAME')
24         Validator('NAME', 'OTHER_NAME', 'EVEN_OTHER')
25         Validator(r'^NAME', r'OTHER./*')
26 
27     The `operations` are::
28 
29         eq: value == other
30         ne: value != other
31         gt: value > other
32         lt: value < other
33         gte: value >= other
34         lte: value <= other
35         is_type_of: isinstance(value, type)
36         is_in:  value in sequence
37         is_not_in: value not in sequence
38         identity: value is other
39         cont: contain value in
40         len_eq: len(value) == other
41         len_ne: len(value) != other
42         len_min: len(value) > other
43         len_max: len(value) < other
44 
45     `env` is which env to be checked, can be a list or
46     default is used.
47 
48     `when` holds a validator and its return decides if validator runs or not::
49 
50         Validator('NAME', must_exist=True, when=Validator('OTHER', eq=2))
51         # NAME is required only if OTHER eq to 2
52         # When the very first thing to be performed when passed.
53         # if no env is passed to `when` it is inherited
54 
55     `must_exist` is alias to `required` requirement. (executed after when)::
56 
57        settings.get(value, empty) returns non empty
58 
59     condition is a callable to be executed and return boolean::
60 
61        Validator('NAME', condition=lambda x: x == 1)
62        # it is executed before operations.
63 
64     """
65 
66     default_messages = MappingProxyType(
67         {
68             "must_exist_true": "{name} is required in env {env}",
69             "must_exist_false": "{name} cannot exists in env {env}",
70             "condition": "{name} invalid for {function}({value}) in env {env}",
71             "operations": (
72                 "{name} must {operation} {op_value} "
73                 "but it is {value} in env {env}"
74             ),
75             "combined": "combined validators failed {errors}",
76         }
77     )
78 
79     def __init__(
80         self,
81         *names,
82         must_exist=None,
83         required=None,  # this is alias for `must_exist`
84         condition=None,
85         when=None,
86         env=None,
87         messages=None,
88         cast=None,
89         default=empty,  # Literal value or a callable
90         **operations
91     ):
92         # Copy immutable MappingProxyType as a mutable dict
93         self.messages = dict(self.default_messages)
94         if messages:
95             self.messages.update(messages)
96 
97         if when is not None and not isinstance(when, Validator):
98             raise TypeError("when must be Validator instance")
99 
100         if condition is not None and not callable(condition):
101             raise TypeError("condition must be callable")
102 
103         self.names = names
104         self.must_exist = must_exist if must_exist is not None else required
105         self.condition = condition
106         self.when = when
107         self.cast = cast or (lambda value: value)
108         self.operations = operations
109         self.default = default
110 
111         if isinstance(env, str):
112             self.envs = [env]
113         elif isinstance(env, (list, tuple)):
114             self.envs = env
115         else:
116             self.envs = None
117 
118     def __or__(self, other):
119         return OrValidator(self, other)
120 
121     def __and__(self, other):
122         return AndValidator(self, other)
123 
124     def __eq__(self, other):
125         if self is other:
126             return True
127 
128         identical_attrs = (
129             getattr(self, attr) == getattr(other, attr)
130             for attr in (
131                 "names",
132                 "must_exist",
133                 "when",
134                 "condition",
135                 "operations",
136                 "envs",
137             )
138         )
139         if all(identical_attrs):
140             return True
141 
142         return False
143 
144     def validate(self, settings):
145         """Raise ValidationError if invalid"""
146 
147         if self.envs is None:
148             self.envs = [settings.current_env]
149 
150         if self.when is not None:
151             try:
152                 # inherit env if not defined
153                 if self.when.envs is None:
154                     self.when.envs = self.envs
155 
156                 self.when.validate(settings)
157             except ValidationError:
158                 # if when is invalid, return canceling validation flow
159                 return
160 
161         # If only using current_env, skip using_env decoration (reload)
162         if (
163             len(self.envs) == 1
164             and self.envs[0].upper() == settings.current_env.upper()
165         ):
166             self._validate_items(settings, settings.current_env)
167             return
168 
169         for env in self.envs:
170             self._validate_items(settings.from_env(env))
171 
172     def _validate_items(self, settings, env=None):
173         env = env or settings.current_env
174         for name in self.names:
175             if self.default is not empty:
176                 default_value = (
177                     self.default(settings, self)
178                     if callable(self.default)
179                     else self.default
180                 )
181             else:
182                 default_value = empty
183 
184             value = self.cast(settings.setdefault(name, default_value))
185 
186             # is name required but not exists?
187             if self.must_exist is True and value is empty:
188                 raise ValidationError(
189                     self.messages["must_exist_true"].format(name=name, env=env)
190                 )
191             elif self.must_exist is False and value is not empty:
192                 raise ValidationError(
193                     self.messages["must_exist_false"].format(
194                         name=name, env=env
195                     )
196                 )
197             elif self.must_exist in (False, None) and value is empty:
198                 continue
199 
200             # is there a callable condition?
201             if self.condition is not None:
202                 if not self.condition(value):
203                     raise ValidationError(
204                         self.messages["condition"].format(
205                             name=name,
206                             function=self.condition.__name__,
207                             value=value,
208                             env=env,
209                         )
210                     )
211 
212             # operations
213             for op_name, op_value in self.operations.items():
214                 op_function = getattr(validator_conditions, op_name)
215                 if not op_function(value, op_value):
216                     raise ValidationError(
217                         self.messages["operations"].format(
218                             name=name,
219                             operation=op_function.__name__,
220                             op_value=op_value,
221                             value=value,
222                             env=env,
223                         )
224                     )
225 
226 
227 class CombinedValidator(Validator):
228     def __init__(self, validator_a, validator_b, *args, **kwargs):
229         """Takes 2 validators and combines the validation"""
230         self.validators = (validator_a, validator_b)
231         super().__init__(*args, **kwargs)
232 
233     def validate(self, settings):  # pragma: no cover
234         raise NotImplementedError(
235             "subclasses OrValidator or AndValidator implements this method"
236         )
237 
238 
239 class OrValidator(CombinedValidator):
240     """Evaluates on Validator() | Validator()"""
241 
242     def validate(self, settings):
243         """Ensure at least one of the validators are valid"""
244         errors = []
245         for validator in self.validators:
246             try:
247                 validator.validate(settings)
248             except ValidationError as e:
249                 errors.append(e)
250                 continue
251             else:
252                 return
253         raise ValidationError(
254             self.messages["combined"].format(
255                 errors=" or ".join(str(e) for e in errors)
256             )
257         )
258 
259 
260 class AndValidator(CombinedValidator):
261     """Evaluates on Validator() & Validator()"""
262 
263     def validate(self, settings):
264         """Ensure both the validators are valid"""
265         errors = []
266         for validator in self.validators:
267             try:
268                 validator.validate(settings)
269             except ValidationError as e:
270                 errors.append(e)
271                 continue
272         if errors:
273             raise ValidationError(
274                 self.messages["combined"].format(
275                     errors=" and ".join(str(e) for e in errors)
276                 )
277             )
278 
279 
280 class ValidatorList(list):
281     def __init__(self, settings, validators=None, *args, **kwargs):
282         if isinstance(validators, (list, tuple)):
283             args = list(args) + list(validators)
284         super(ValidatorList, self).__init__(args, **kwargs)
285         self.settings = settings
286 
287     def register(self, *args, **kwargs):
288         validators = list(chain.from_iterable(kwargs.values()))
289         validators.extend(args)
290         for validator in validators:
291             if validator and validator not in self:
292                 self.append(validator)
293 
294     def validate(self):
295         for validator in self:
296             validator.validate(self.settings)
297
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/dynaconf/validator.py b/dynaconf/validator.py
--- a/dynaconf/validator.py
+++ b/dynaconf/validator.py
@@ -5,6 +5,16 @@
 from dynaconf.utils.functional import empty
 
 
+EQUALITY_ATTRS = (
+    "names",
+    "must_exist",
+    "when",
+    "condition",
+    "operations",
+    "envs",
+)
+
+
 class ValidationError(Exception):
     pass
 
@@ -125,16 +135,12 @@
         if self is other:
             return True
 
+        if type(self).__name__ != type(other).__name__:
+            return False
+
         identical_attrs = (
             getattr(self, attr) == getattr(other, attr)
-            for attr in (
-                "names",
-                "must_exist",
-                "when",
-                "condition",
-                "operations",
-                "envs",
-            )
+            for attr in EQUALITY_ATTRS
         )
         if all(identical_attrs):
             return True
@@ -229,6 +235,12 @@
         """Takes 2 validators and combines the validation"""
         self.validators = (validator_a, validator_b)
         super().__init__(*args, **kwargs)
+        for attr in EQUALITY_ATTRS:
+            if not getattr(self, attr, None):
+                value = tuple(
+                    getattr(validator, attr) for validator in self.validators
+                )
+                setattr(self, attr, value)
 
     def validate(self, settings):  # pragma: no cover
         raise NotImplementedError(

{"golden_diff": "diff --git a/dynaconf/validator.py b/dynaconf/validator.py\n--- a/dynaconf/validator.py\n+++ b/dynaconf/validator.py\n@@ -5,6 +5,16 @@\n from dynaconf.utils.functional import empty\n \n \n+EQUALITY_ATTRS = (\n+    \"names\",\n+    \"must_exist\",\n+    \"when\",\n+    \"condition\",\n+    \"operations\",\n+    \"envs\",\n+)\n+\n+\n class ValidationError(Exception):\n     pass\n \n@@ -125,16 +135,12 @@\n         if self is other:\n             return True\n \n+        if type(self).__name__ != type(other).__name__:\n+            return False\n+\n         identical_attrs = (\n             getattr(self, attr) == getattr(other, attr)\n-            for attr in (\n-                \"names\",\n-                \"must_exist\",\n-                \"when\",\n-                \"condition\",\n-                \"operations\",\n-                \"envs\",\n-            )\n+            for attr in EQUALITY_ATTRS\n         )\n         if all(identical_attrs):\n             return True\n@@ -229,6 +235,12 @@\n         \"\"\"Takes 2 validators and combines the validation\"\"\"\n         self.validators = (validator_a, validator_b)\n         super().__init__(*args, **kwargs)\n+        for attr in EQUALITY_ATTRS:\n+            if not getattr(self, attr, None):\n+                value = tuple(\n+                    getattr(validator, attr) for validator in self.validators\n+                )\n+                setattr(self, attr, value)\n \n     def validate(self, settings):  # pragma: no cover\n         raise NotImplementedError(\n", "issue": "[bug] Only one CombinedValidator is registered - subsequent are silently ignored\n**Describe the bug**\r\nIf validators are added through `settings.validators.register()`, only first CombinedValidator is registered - subsequent are silently skipped.\r\n\r\n**Analysis**\r\nThe root cause is `Validator.__eq__()` method. `ValidatorList.register()` will go through provided validators and add them, but only if they aren't already on the list (`validator not in self`). `in` will return \"`True` if an item of *s* is equal to *x*\" ([docs](https://docs.python.org/3.8/library/stdtypes.html#common-sequence-operations)). That logic was added in #256 .\r\n\r\nWhen `Validator.__eq__()` compares different objects, it looks into various `Validator` properties and compares them in pair. If they are all the same, `__eq__()` will assume these are two instances of effectively the same validation rule.\r\n\r\nThe problem is, `CombinedValidator` doesn't have any of these properties, so two completely different `CombinedValidator` will appear to be the same for `__eq__()` method.\r\n\r\n\r\n**To Reproduce**\r\nIn python shell:\r\n```\r\n>>> from dynaconf import Validator\r\n>>> (Validator(\"foo.foo\") | Validator(\"foo.bar\")) == (Validator(\"bar.foo\") & Validator(\"bar.bar\"))\r\nTrue\r\n```\r\n\r\nThis should return False, as these two `CombinedValidator`s have nothing in common.\r\n\r\n**Environment (please complete the following information):**\r\n - OS: Linux/Fedora32\r\n - Dynaconf master (6c568d687e29ca5ed9806a74f1f4fb7e4b96be2f), 3.0.1\r\n\r\n**Additional context**\r\nI might try working on patch, but I'm not sure about best approach. Perhaps we need type comparison inside `__eq__()` (so comparing AndValidator to OrValidator can return early). But how do we compare two `AndValidator`s? Look into combined validators properties recursively?\n", "before_files": [{"content": "from itertools import chain\nfrom types import MappingProxyType\n\nfrom dynaconf import validator_conditions  # noqa\nfrom dynaconf.utils.functional import empty\n\n\nclass ValidationError(Exception):\n    pass\n\n\nclass Validator(object):\n    \"\"\"Validators are conditions attached to settings variables names\n    or patterns::\n\n        Validator('MESSAGE', must_exist=True, eq='Hello World')\n\n    The above ensure MESSAGE is available in default env and\n    is equal to 'Hello World'\n\n    `names` are a one (or more) names or patterns::\n\n        Validator('NAME')\n        Validator('NAME', 'OTHER_NAME', 'EVEN_OTHER')\n        Validator(r'^NAME', r'OTHER./*')\n\n    The `operations` are::\n\n        eq: value == other\n        ne: value != other\n        gt: value > other\n        lt: value < other\n        gte: value >= other\n        lte: value <= other\n        is_type_of: isinstance(value, type)\n        is_in:  value in sequence\n        is_not_in: value not in sequence\n        identity: value is other\n        cont: contain value in\n        len_eq: len(value) == other\n        len_ne: len(value) != other\n        len_min: len(value) > other\n        len_max: len(value) < other\n\n    `env` is which env to be checked, can be a list or\n    default is used.\n\n    `when` holds a validator and its return decides if validator runs or not::\n\n        Validator('NAME', must_exist=True, when=Validator('OTHER', eq=2))\n        # NAME is required only if OTHER eq to 2\n        # When the very first thing to be performed when passed.\n        # if no env is passed to `when` it is inherited\n\n    `must_exist` is alias to `required` requirement. (executed after when)::\n\n       settings.get(value, empty) returns non empty\n\n    condition is a callable to be executed and return boolean::\n\n       Validator('NAME', condition=lambda x: x == 1)\n       # it is executed before operations.\n\n    \"\"\"\n\n    default_messages = MappingProxyType(\n        {\n            \"must_exist_true\": \"{name} is required in env {env}\",\n            \"must_exist_false\": \"{name} cannot exists in env {env}\",\n            \"condition\": \"{name} invalid for {function}({value}) in env {env}\",\n            \"operations\": (\n                \"{name} must {operation} {op_value} \"\n                \"but it is {value} in env {env}\"\n            ),\n            \"combined\": \"combined validators failed {errors}\",\n        }\n    )\n\n    def __init__(\n        self,\n        *names,\n        must_exist=None,\n        required=None,  # this is alias for `must_exist`\n        condition=None,\n        when=None,\n        env=None,\n        messages=None,\n        cast=None,\n        default=empty,  # Literal value or a callable\n        **operations\n    ):\n        # Copy immutable MappingProxyType as a mutable dict\n        self.messages = dict(self.default_messages)\n        if messages:\n            self.messages.update(messages)\n\n        if when is not None and not isinstance(when, Validator):\n            raise TypeError(\"when must be Validator instance\")\n\n        if condition is not None and not callable(condition):\n            raise TypeError(\"condition must be callable\")\n\n        self.names = names\n        self.must_exist = must_exist if must_exist is not None else required\n        self.condition = condition\n        self.when = when\n        self.cast = cast or (lambda value: value)\n        self.operations = operations\n        self.default = default\n\n        if isinstance(env, str):\n            self.envs = [env]\n        elif isinstance(env, (list, tuple)):\n            self.envs = env\n        else:\n            self.envs = None\n\n    def __or__(self, other):\n        return OrValidator(self, other)\n\n    def __and__(self, other):\n        return AndValidator(self, other)\n\n    def __eq__(self, other):\n        if self is other:\n            return True\n\n        identical_attrs = (\n            getattr(self, attr) == getattr(other, attr)\n            for attr in (\n                \"names\",\n                \"must_exist\",\n                \"when\",\n                \"condition\",\n                \"operations\",\n                \"envs\",\n            )\n        )\n        if all(identical_attrs):\n            return True\n\n        return False\n\n    def validate(self, settings):\n        \"\"\"Raise ValidationError if invalid\"\"\"\n\n        if self.envs is None:\n            self.envs = [settings.current_env]\n\n        if self.when is not None:\n            try:\n                # inherit env if not defined\n                if self.when.envs is None:\n                    self.when.envs = self.envs\n\n                self.when.validate(settings)\n            except ValidationError:\n                # if when is invalid, return canceling validation flow\n                return\n\n        # If only using current_env, skip using_env decoration (reload)\n        if (\n            len(self.envs) == 1\n            and self.envs[0].upper() == settings.current_env.upper()\n        ):\n            self._validate_items(settings, settings.current_env)\n            return\n\n        for env in self.envs:\n            self._validate_items(settings.from_env(env))\n\n    def _validate_items(self, settings, env=None):\n        env = env or settings.current_env\n        for name in self.names:\n            if self.default is not empty:\n                default_value = (\n                    self.default(settings, self)\n                    if callable(self.default)\n                    else self.default\n                )\n            else:\n                default_value = empty\n\n            value = self.cast(settings.setdefault(name, default_value))\n\n            # is name required but not exists?\n            if self.must_exist is True and value is empty:\n                raise ValidationError(\n                    self.messages[\"must_exist_true\"].format(name=name, env=env)\n                )\n            elif self.must_exist is False and value is not empty:\n                raise ValidationError(\n                    self.messages[\"must_exist_false\"].format(\n                        name=name, env=env\n                    )\n                )\n            elif self.must_exist in (False, None) and value is empty:\n                continue\n\n            # is there a callable condition?\n            if self.condition is not None:\n                if not self.condition(value):\n                    raise ValidationError(\n                        self.messages[\"condition\"].format(\n                            name=name,\n                            function=self.condition.__name__,\n                            value=value,\n                            env=env,\n                        )\n                    )\n\n            # operations\n            for op_name, op_value in self.operations.items():\n                op_function = getattr(validator_conditions, op_name)\n                if not op_function(value, op_value):\n                    raise ValidationError(\n                        self.messages[\"operations\"].format(\n                            name=name,\n                            operation=op_function.__name__,\n                            op_value=op_value,\n                            value=value,\n                            env=env,\n                        )\n                    )\n\n\nclass CombinedValidator(Validator):\n    def __init__(self, validator_a, validator_b, *args, **kwargs):\n        \"\"\"Takes 2 validators and combines the validation\"\"\"\n        self.validators = (validator_a, validator_b)\n        super().__init__(*args, **kwargs)\n\n    def validate(self, settings):  # pragma: no cover\n        raise NotImplementedError(\n            \"subclasses OrValidator or AndValidator implements this method\"\n        )\n\n\nclass OrValidator(CombinedValidator):\n    \"\"\"Evaluates on Validator() | Validator()\"\"\"\n\n    def validate(self, settings):\n        \"\"\"Ensure at least one of the validators are valid\"\"\"\n        errors = []\n        for validator in self.validators:\n            try:\n                validator.validate(settings)\n            except ValidationError as e:\n                errors.append(e)\n                continue\n            else:\n                return\n        raise ValidationError(\n            self.messages[\"combined\"].format(\n                errors=\" or \".join(str(e) for e in errors)\n            )\n        )\n\n\nclass AndValidator(CombinedValidator):\n    \"\"\"Evaluates on Validator() & Validator()\"\"\"\n\n    def validate(self, settings):\n        \"\"\"Ensure both the validators are valid\"\"\"\n        errors = []\n        for validator in self.validators:\n            try:\n                validator.validate(settings)\n            except ValidationError as e:\n                errors.append(e)\n                continue\n        if errors:\n            raise ValidationError(\n                self.messages[\"combined\"].format(\n                    errors=\" and \".join(str(e) for e in errors)\n                )\n            )\n\n\nclass ValidatorList(list):\n    def __init__(self, settings, validators=None, *args, **kwargs):\n        if isinstance(validators, (list, tuple)):\n            args = list(args) + list(validators)\n        super(ValidatorList, self).__init__(args, **kwargs)\n        self.settings = settings\n\n    def register(self, *args, **kwargs):\n        validators = list(chain.from_iterable(kwargs.values()))\n        validators.extend(args)\n        for validator in validators:\n            if validator and validator not in self:\n                self.append(validator)\n\n    def validate(self):\n        for validator in self:\n            validator.validate(self.settings)\n", "path": "dynaconf/validator.py"}], "after_files": [{"content": "from itertools import chain\nfrom types import MappingProxyType\n\nfrom dynaconf import validator_conditions  # noqa\nfrom dynaconf.utils.functional import empty\n\n\nEQUALITY_ATTRS = (\n    \"names\",\n    \"must_exist\",\n    \"when\",\n    \"condition\",\n    \"operations\",\n    \"envs\",\n)\n\n\nclass ValidationError(Exception):\n    pass\n\n\nclass Validator(object):\n    \"\"\"Validators are conditions attached to settings variables names\n    or patterns::\n\n        Validator('MESSAGE', must_exist=True, eq='Hello World')\n\n    The above ensure MESSAGE is available in default env and\n    is equal to 'Hello World'\n\n    `names` are a one (or more) names or patterns::\n\n        Validator('NAME')\n        Validator('NAME', 'OTHER_NAME', 'EVEN_OTHER')\n        Validator(r'^NAME', r'OTHER./*')\n\n    The `operations` are::\n\n        eq: value == other\n        ne: value != other\n        gt: value > other\n        lt: value < other\n        gte: value >= other\n        lte: value <= other\n        is_type_of: isinstance(value, type)\n        is_in:  value in sequence\n        is_not_in: value not in sequence\n        identity: value is other\n        cont: contain value in\n        len_eq: len(value) == other\n        len_ne: len(value) != other\n        len_min: len(value) > other\n        len_max: len(value) < other\n\n    `env` is which env to be checked, can be a list or\n    default is used.\n\n    `when` holds a validator and its return decides if validator runs or not::\n\n        Validator('NAME', must_exist=True, when=Validator('OTHER', eq=2))\n        # NAME is required only if OTHER eq to 2\n        # When the very first thing to be performed when passed.\n        # if no env is passed to `when` it is inherited\n\n    `must_exist` is alias to `required` requirement. (executed after when)::\n\n       settings.get(value, empty) returns non empty\n\n    condition is a callable to be executed and return boolean::\n\n       Validator('NAME', condition=lambda x: x == 1)\n       # it is executed before operations.\n\n    \"\"\"\n\n    default_messages = MappingProxyType(\n        {\n            \"must_exist_true\": \"{name} is required in env {env}\",\n            \"must_exist_false\": \"{name} cannot exists in env {env}\",\n            \"condition\": \"{name} invalid for {function}({value}) in env {env}\",\n            \"operations\": (\n                \"{name} must {operation} {op_value} \"\n                \"but it is {value} in env {env}\"\n            ),\n            \"combined\": \"combined validators failed {errors}\",\n        }\n    )\n\n    def __init__(\n        self,\n        *names,\n        must_exist=None,\n        required=None,  # this is alias for `must_exist`\n        condition=None,\n        when=None,\n        env=None,\n        messages=None,\n        cast=None,\n        default=empty,  # Literal value or a callable\n        **operations\n    ):\n        # Copy immutable MappingProxyType as a mutable dict\n        self.messages = dict(self.default_messages)\n        if messages:\n            self.messages.update(messages)\n\n        if when is not None and not isinstance(when, Validator):\n            raise TypeError(\"when must be Validator instance\")\n\n        if condition is not None and not callable(condition):\n            raise TypeError(\"condition must be callable\")\n\n        self.names = names\n        self.must_exist = must_exist if must_exist is not None else required\n        self.condition = condition\n        self.when = when\n        self.cast = cast or (lambda value: value)\n        self.operations = operations\n        self.default = default\n\n        if isinstance(env, str):\n            self.envs = [env]\n        elif isinstance(env, (list, tuple)):\n            self.envs = env\n        else:\n            self.envs = None\n\n    def __or__(self, other):\n        return OrValidator(self, other)\n\n    def __and__(self, other):\n        return AndValidator(self, other)\n\n    def __eq__(self, other):\n        if self is other:\n            return True\n\n        if type(self).__name__ != type(other).__name__:\n            return False\n\n        identical_attrs = (\n            getattr(self, attr) == getattr(other, attr)\n            for attr in EQUALITY_ATTRS\n        )\n        if all(identical_attrs):\n            return True\n\n        return False\n\n    def validate(self, settings):\n        \"\"\"Raise ValidationError if invalid\"\"\"\n\n        if self.envs is None:\n            self.envs = [settings.current_env]\n\n        if self.when is not None:\n            try:\n                # inherit env if not defined\n                if self.when.envs is None:\n                    self.when.envs = self.envs\n\n                self.when.validate(settings)\n            except ValidationError:\n                # if when is invalid, return canceling validation flow\n                return\n\n        # If only using current_env, skip using_env decoration (reload)\n        if (\n            len(self.envs) == 1\n            and self.envs[0].upper() == settings.current_env.upper()\n        ):\n            self._validate_items(settings, settings.current_env)\n            return\n\n        for env in self.envs:\n            self._validate_items(settings.from_env(env))\n\n    def _validate_items(self, settings, env=None):\n        env = env or settings.current_env\n        for name in self.names:\n            if self.default is not empty:\n                default_value = (\n                    self.default(settings, self)\n                    if callable(self.default)\n                    else self.default\n                )\n            else:\n                default_value = empty\n\n            value = self.cast(settings.setdefault(name, default_value))\n\n            # is name required but not exists?\n            if self.must_exist is True and value is empty:\n                raise ValidationError(\n                    self.messages[\"must_exist_true\"].format(name=name, env=env)\n                )\n            elif self.must_exist is False and value is not empty:\n                raise ValidationError(\n                    self.messages[\"must_exist_false\"].format(\n                        name=name, env=env\n                    )\n                )\n            elif self.must_exist in (False, None) and value is empty:\n                continue\n\n            # is there a callable condition?\n            if self.condition is not None:\n                if not self.condition(value):\n                    raise ValidationError(\n                        self.messages[\"condition\"].format(\n                            name=name,\n                            function=self.condition.__name__,\n                            value=value,\n                            env=env,\n                        )\n                    )\n\n            # operations\n            for op_name, op_value in self.operations.items():\n                op_function = getattr(validator_conditions, op_name)\n                if not op_function(value, op_value):\n                    raise ValidationError(\n                        self.messages[\"operations\"].format(\n                            name=name,\n                            operation=op_function.__name__,\n                            op_value=op_value,\n                            value=value,\n                            env=env,\n                        )\n                    )\n\n\nclass CombinedValidator(Validator):\n    def __init__(self, validator_a, validator_b, *args, **kwargs):\n        \"\"\"Takes 2 validators and combines the validation\"\"\"\n        self.validators = (validator_a, validator_b)\n        super().__init__(*args, **kwargs)\n        for attr in EQUALITY_ATTRS:\n            if not getattr(self, attr, None):\n                value = tuple(\n                    getattr(validator, attr) for validator in self.validators\n                )\n                setattr(self, attr, value)\n\n    def validate(self, settings):  # pragma: no cover\n        raise NotImplementedError(\n            \"subclasses OrValidator or AndValidator implements this method\"\n        )\n\n\nclass OrValidator(CombinedValidator):\n    \"\"\"Evaluates on Validator() | Validator()\"\"\"\n\n    def validate(self, settings):\n        \"\"\"Ensure at least one of the validators are valid\"\"\"\n        errors = []\n        for validator in self.validators:\n            try:\n                validator.validate(settings)\n            except ValidationError as e:\n                errors.append(e)\n                continue\n            else:\n                return\n        raise ValidationError(\n            self.messages[\"combined\"].format(\n                errors=\" or \".join(str(e) for e in errors)\n            )\n        )\n\n\nclass AndValidator(CombinedValidator):\n    \"\"\"Evaluates on Validator() & Validator()\"\"\"\n\n    def validate(self, settings):\n        \"\"\"Ensure both the validators are valid\"\"\"\n        errors = []\n        for validator in self.validators:\n            try:\n                validator.validate(settings)\n            except ValidationError as e:\n                errors.append(e)\n                continue\n        if errors:\n            raise ValidationError(\n                self.messages[\"combined\"].format(\n                    errors=\" and \".join(str(e) for e in errors)\n                )\n            )\n\n\nclass ValidatorList(list):\n    def __init__(self, settings, validators=None, *args, **kwargs):\n        if isinstance(validators, (list, tuple)):\n            args = list(args) + list(validators)\n        super(ValidatorList, self).__init__(args, **kwargs)\n        self.settings = settings\n\n    def register(self, *args, **kwargs):\n        validators = list(chain.from_iterable(kwargs.values()))\n        validators.extend(args)\n        for validator in validators:\n            if validator and validator not in self:\n                self.append(validator)\n\n    def validate(self):\n        for validator in self:\n            validator.validate(self.settings)\n", "path": "dynaconf/validator.py"}]}