@@ -100,27 +100,52 @@ def _get_values_for_config(self, config_schema_db, config_db):
100100        return  config 
101101
102102    @staticmethod  
103-     def  _get_object_property_schema (object_schema , additional_properties_keys = None ):
103+     def  _get_object_properties_schema (object_schema , objecy_keys = None ):
104104        """ 
105105        Create a schema for an object property using all of: properties, 
106106        patternProperties, and additionalProperties. 
107107
108+         This 'flattens' properties, patternProperties, and additionalProperties 
109+         so that we can handle patternProperties and additionalProperties 
110+         as if they were defined in properties. 
111+         So, every key in objecy_keys will be assigned a schema 
112+         from properties, patternProperties, or additionalProperties. 
113+ 
114+         NOTE: order of precedence: properties, patternProperties, additionalProperties 
115+         So, the additionalProperties schema is only used for keys that are not in 
116+         properties and that do not match any of the patterns in patternProperties. 
117+         And, patternProperties schemas only apply to keys missing from properties. 
118+ 
108119        :rtype: ``dict`` 
109120        """ 
110-         property_schema  =  {}
121+         flattened_properties_schema  =  {}
122+ 
123+         # First, eagerly add the additionalProperties schema for all object_keys to 
124+         # avoid tracking which keys are covered by patternProperties and properties. 
125+         # This schema will subsequently be replaced by the more-specific key matches 
126+         # in patternProperties and properties. 
127+ 
111128        additional_properties  =  object_schema .get ("additionalProperties" , {})
112129        # additionalProperties can be a boolean or a dict 
113130        if  additional_properties  and  isinstance (additional_properties , dict ):
114131            # ensure that these keys are present in the object 
115-             for  key  in  additional_properties_keys :
116-                 property_schema [key ] =  additional_properties 
132+             for  key  in  objecy_keys :
133+                 flattened_properties_schema [key ] =  additional_properties 
134+ 
135+         # Second, replace the additionalProperties schemas with any 
136+         # explicit property schemas in propertiea. 
117137
118138        properties_schema  =  object_schema .get ("properties" , {})
119-         property_schema .update (properties_schema )
139+         flattened_properties_schema .update (properties_schema )
120140
121-         potential_patterned_keys  =  set (additional_properties_keys ) -  set (
122-             properties_schema .keys ()
123-         )
141+         # Third, calculate which keys are in object_keys but not in properties. 
142+         # These are the only keys that can be matched with patternnProperties. 
143+ 
144+         potential_patterned_keys  =  set (objecy_keys ) -  set (properties_schema .keys ())
145+ 
146+         # Fourth, match the remaining keys with patternProperties, 
147+         # and replace the additionalProperties schema with the patternProperties schema 
148+         # because patternProperties is more specific than additionalProperties. 
124149
125150        pattern_properties  =  object_schema .get ("patternProperties" , {})
126151        # patternProperties can be a boolean or a dict 
@@ -133,9 +158,9 @@ def _get_object_property_schema(object_schema, additional_properties_keys=None):
133158                pattern  =  re .compile (raw_pattern )
134159                for  key  in  list (potential_patterned_keys ):
135160                    if  pattern .search (key ):
136-                         property_schema [key ] =  pattern_schema 
161+                         flattened_properties_schema [key ] =  pattern_schema 
137162                        potential_patterned_keys .remove (key )
138-         return  property_schema 
163+         return  flattened_properties_schema 
139164
140165    @staticmethod  
141166    def  _get_array_items_schema (object_schema , items_count = 0 ):
@@ -204,12 +229,12 @@ def _assign_dynamic_config_values(self, schema, config, parent_keys=None):
204229
205230            # Inspect nested object properties 
206231            if  is_dictionary :
207-                 property_schema  =  self ._get_object_property_schema (
232+                 properties_schema  =  self ._get_object_properties_schema (
208233                    schema_item ,
209-                     additional_properties_keys = config_item_value .keys (),
234+                     objecy_keys = config_item_value .keys (),
210235                )
211236                self ._assign_dynamic_config_values (
212-                     schema = property_schema ,
237+                     schema = properties_schema ,
213238                    config = config [config_item_key ],
214239                    parent_keys = current_keys ,
215240                )
@@ -293,13 +318,13 @@ def _assign_default_values(self, schema, config):
293318                    if  not  config_value :
294319                        config_value  =  config [schema_item_key ] =  {}
295320
296-                     property_schema  =  self ._get_object_property_schema (
321+                     properties_schema  =  self ._get_object_properties_schema (
297322                        schema_item ,
298-                         additional_properties_keys = config_value .keys (),
323+                         objecy_keys = config_value .keys (),
299324                    )
300325
301326                    self ._assign_default_values (
302-                         schema = property_schema , config = config_value 
327+                         schema = properties_schema , config = config_value 
303328                    )
304329            elif  schema_item_type  ==  "array" :
305330                has_items  =  schema_item .get ("items" , None )
0 commit comments