alecthomas: Aaah, nice find. Thank you for the PR. Just one minor change and I'm happy to merge.
CedricCabessa: I just amend my commit with your remark. Thanks

However, I just found out that some case do not work :-(

like "dict in dict", the empty dict is considered valid but should not

```
schema = Schema(Any({'a': Any(float, int)},
                         {'b': int},
                         {'c' : {'aa': int}},
                         required=True))
```

If it's ok, I'll try to come back to you with another commit on this branch to fix that.
Or you can merge this and I'll open another PR, as you prefer

CedricCabessa: Here is the 2nd part of the patch to allow "recursive schema"
Let me know what you think :-)
diff --git a/voluptuous/validators.py b/voluptuous/validators.py
index 8c6a86d..81566b9 100644
--- a/voluptuous/validators.py
+++ b/voluptuous/validators.py
@@ -192,12 +192,13 @@ class _WithSubValidators(object):
     def __init__(self, *validators, **kwargs):
         self.validators = validators
         self.msg = kwargs.pop('msg', None)
+        self.required = kwargs.pop('required', False)
 
     def __voluptuous_compile__(self, schema):
-        self._compiled = [
-            schema._compile(v)
-            for v in self.validators
-        ]
+        self._compiled = []
+        for v in self.validators:
+            schema.required = self.required
+            self._compiled.append(schema._compile(v))
         return self._run
 
     def _run(self, path, value):

Regression with Any and required
Hi,

I've observed a change of behaviour in the recent version of voluptuous

Here is a sample

```
from voluptuous import Schema, Any, Optional, All, Length

anyschema = Schema(Any({
    'name': All(unicode, Length(min=1, max=128)),
    Optional('protected'): bool,
}, {
    'name': All(unicode, Length(min=1, max=128)),
    Optional('dynamic_ip_required'): bool,
}, required=True))

try:
    anyschema({})
except Exception as e:
    print(e)
```	

```
$ sudo pip install 'voluptuous==0.11.5'
$ python t.py 
```
no output

```
$ sudo pip install 'voluptuous==0.10.5'
$ python t.py 
required key not provided @ data['name']
```

My understanding is the behavior of 0.10.5 is correct as we want a "required schema"
But it is not the case with 0.11.5 and above, required seem to be ignored

Is it normal?


diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py
index bc16a12..f2db8da 100644
--- a/voluptuous/tests/tests.py
+++ b/voluptuous/tests/tests.py
@@ -1322,3 +1322,30 @@ def test_strip_util_handles_various_inputs():
     assert Strip(u"3") == u"3"
     assert Strip(b'\xe2\x98\x83'.decode("UTF-8")) == b'\xe2\x98\x83'.decode("UTF-8")
     assert Strip(u" aaa ") == u"aaa"
+
+
+def test_any_required():
+    schema = Schema(Any({'a': int}, {'b': str}, required=True))
+
+    try:
+        schema({})
+    except MultipleInvalid as e:
+        assert_equal(str(e),
+                     "required key not provided @ data['a']")
+    else:
+        assert False, "Did not raise Invalid for MultipleInvalid"
+
+
+def test_any_required_with_subschema():
+    schema = Schema(Any({'a': Any(float, int)},
+                        {'b': int},
+                        {'c': {'aa': int}},
+                    required=True))
+
+    try:
+        schema({})
+    except MultipleInvalid as e:
+        assert_equal(str(e),
+                     "required key not provided @ data['a']")
+    else:
+        assert False, "Did not raise Invalid for MultipleInvalid"

pip install -e .
parse_log_pytest
pytest
nose pytest
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
certifi==2021.5.30
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
nose==1.3.7
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
-e git+https://github.com/alecthomas/voluptuous.git@7e18e1cf505f3a5d859546871e096481636fa21f#egg=voluptuous
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: voluptuous
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - nose==1.3.7
prefix: /opt/conda/envs/voluptuous

voluptuous/tests/tests.py::test_any_required voluptuous/tests/tests.py::test_any_required_with_subschema
voluptuous/tests/tests.py::test_exact_sequence voluptuous/tests/tests.py::test_required voluptuous/tests/tests.py::test_extra_with_required voluptuous/tests/tests.py::test_iterate_candidates voluptuous/tests/tests.py::test_in voluptuous/tests/tests.py::test_not_in voluptuous/tests/tests.py::test_contains voluptuous/tests/tests.py::test_remove voluptuous/tests/tests.py::test_extra_empty_errors voluptuous/tests/tests.py::test_literal voluptuous/tests/tests.py::test_class voluptuous/tests/tests.py::test_email_validation voluptuous/tests/tests.py::test_email_validation_with_none voluptuous/tests/tests.py::test_email_validation_with_empty_string voluptuous/tests/tests.py::test_email_validation_without_host voluptuous/tests/tests.py::test_fqdn_url_validation voluptuous/tests/tests.py::test_fqdn_url_without_domain_name voluptuous/tests/tests.py::test_fqdnurl_validation_with_none voluptuous/tests/tests.py::test_fqdnurl_validation_with_empty_string voluptuous/tests/tests.py::test_fqdnurl_validation_without_host voluptuous/tests/tests.py::test_url_validation voluptuous/tests/tests.py::test_url_validation_with_none voluptuous/tests/tests.py::test_url_validation_with_empty_string voluptuous/tests/tests.py::test_url_validation_without_host voluptuous/tests/tests.py::test_copy_dict_undefined voluptuous/tests/tests.py::test_sorting voluptuous/tests/tests.py::test_schema_extend voluptuous/tests/tests.py::test_schema_extend_overrides voluptuous/tests/tests.py::test_schema_extend_key_swap voluptuous/tests/tests.py::test_subschema_extension voluptuous/tests/tests.py::test_schema_extend_handles_schema_subclass voluptuous/tests/tests.py::test_equality voluptuous/tests/tests.py::test_equality_negative voluptuous/tests/tests.py::test_inequality voluptuous/tests/tests.py::test_inequality_negative voluptuous/tests/tests.py::test_repr voluptuous/tests/tests.py::test_list_validation_messages voluptuous/tests/tests.py::test_nested_multiple_validation_errors voluptuous/tests/tests.py::test_humanize_error voluptuous/tests/tests.py::test_fix_157 voluptuous/tests/tests.py::test_range_exlcudes_nan voluptuous/tests/tests.py::test_equal voluptuous/tests/tests.py::test_unordered voluptuous/tests/tests.py::test_maybe voluptuous/tests/tests.py::test_maybe_accepts_msg voluptuous/tests/tests.py::test_empty_list_as_exact voluptuous/tests/tests.py::test_schema_decorator_match_with_args voluptuous/tests/tests.py::test_schema_decorator_unmatch_with_args voluptuous/tests/tests.py::test_schema_decorator_match_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_unmatch_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_match_return_with_args voluptuous/tests/tests.py::test_schema_decorator_unmatch_return_with_args voluptuous/tests/tests.py::test_schema_decorator_match_return_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_unmatch_return_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_return_only_match voluptuous/tests/tests.py::test_schema_decorator_return_only_unmatch voluptuous/tests/tests.py::test_schema_decorator_partial_match_called_with_args voluptuous/tests/tests.py::test_schema_decorator_partial_unmatch_called_with_args voluptuous/tests/tests.py::test_schema_decorator_partial_match_called_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_partial_unmatch_called_with_kwargs voluptuous/tests/tests.py::test_unicode_as_key voluptuous/tests/tests.py::test_number_validation_with_string voluptuous/tests/tests.py::test_number_validation_with_invalid_precision_invalid_scale voluptuous/tests/tests.py::test_number_validation_with_valid_precision_scale_yield_decimal_true voluptuous/tests/tests.py::test_number_when_precision_scale_none_yield_decimal_true voluptuous/tests/tests.py::test_number_when_precision_none_n_valid_scale_case1_yield_decimal_true voluptuous/tests/tests.py::test_number_when_precision_none_n_valid_scale_case2_yield_decimal_true voluptuous/tests/tests.py::test_number_when_precision_none_n_invalid_scale_yield_decimal_true voluptuous/tests/tests.py::test_number_when_valid_precision_n_scale_none_yield_decimal_true voluptuous/tests/tests.py::test_number_when_invalid_precision_n_scale_none_yield_decimal_true voluptuous/tests/tests.py::test_number_validation_with_valid_precision_scale_yield_decimal_false voluptuous/tests/tests.py::test_named_tuples_validate_as_tuples voluptuous/tests/tests.py::test_datetime voluptuous/tests/tests.py::test_date voluptuous/tests/tests.py::test_date_custom_format voluptuous/tests/tests.py::test_ordered_dict voluptuous/tests/tests.py::test_marker_hashable voluptuous/tests/tests.py::test_schema_infer voluptuous/tests/tests.py::test_schema_infer_dict voluptuous/tests/tests.py::test_schema_infer_list voluptuous/tests/tests.py::test_schema_infer_scalar voluptuous/tests/tests.py::test_schema_infer_accepts_kwargs voluptuous/tests/tests.py::test_validation_performance voluptuous/tests/tests.py::test_IsDir voluptuous/tests/tests.py::test_IsFile voluptuous/tests/tests.py::test_PathExists voluptuous/tests/tests.py::test_description voluptuous/tests/tests.py::test_SomeOf_min_validation voluptuous/tests/tests.py::test_SomeOf_max_validation voluptuous/tests/tests.py::test_self_validation voluptuous/tests/tests.py::test_any_error_has_path voluptuous/tests/tests.py::test_all_error_has_path voluptuous/tests/tests.py::test_match_error_has_path voluptuous/tests/tests.py::test_self_any voluptuous/tests/tests.py::test_self_all voluptuous/tests/tests.py::test_SomeOf_on_bounds_assertion voluptuous/tests/tests.py::test_comparing_voluptuous_object_to_str voluptuous/tests/tests.py::test_set_of_integers voluptuous/tests/tests.py::test_frozenset_of_integers voluptuous/tests/tests.py::test_set_of_integers_and_strings voluptuous/tests/tests.py::test_frozenset_of_integers_and_strings voluptuous/tests/tests.py::test_lower_util_handles_various_inputs voluptuous/tests/tests.py::test_upper_util_handles_various_inputs voluptuous/tests/tests.py::test_capitalize_util_handles_various_inputs voluptuous/tests/tests.py::test_title_util_handles_various_inputs voluptuous/tests/tests.py::test_strip_util_handles_various_inputs
voluptuous/validators.py
voluptuous/validators.py