@@ -100,28 +100,55 @@ 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 )
140+
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.
120143
121- potential_patterned_keys = set (additional_properties_keys ) - set (
144+ potential_patterned_keys = set (objecy_keys ) - set (
122145 properties_schema .keys ()
123146 )
124147
148+ # Fourth, match the remaining keys with patternProperties,
149+ # and replace the additionalProperties schema with the patternProperties schema
150+ # because patternProperties is more specific than additionalProperties.
151+
125152 pattern_properties = object_schema .get ("patternProperties" , {})
126153 # patternProperties can be a boolean or a dict
127154 if pattern_properties and isinstance (pattern_properties , dict ):
@@ -133,9 +160,9 @@ def _get_object_property_schema(object_schema, additional_properties_keys=None):
133160 pattern = re .compile (raw_pattern )
134161 for key in list (potential_patterned_keys ):
135162 if pattern .search (key ):
136- property_schema [key ] = pattern_schema
163+ flattened_properties_schema [key ] = pattern_schema
137164 potential_patterned_keys .remove (key )
138- return property_schema
165+ return flattened_properties_schema
139166
140167 @staticmethod
141168 def _get_array_items_schema (object_schema , items_count = 0 ):
@@ -204,12 +231,12 @@ def _assign_dynamic_config_values(self, schema, config, parent_keys=None):
204231
205232 # Inspect nested object properties
206233 if is_dictionary :
207- property_schema = self ._get_object_property_schema (
234+ properties_schema = self ._get_object_properties_schema (
208235 schema_item ,
209- additional_properties_keys = config_item_value .keys (),
236+ objecy_keys = config_item_value .keys (),
210237 )
211238 self ._assign_dynamic_config_values (
212- schema = property_schema ,
239+ schema = properties_schema ,
213240 config = config [config_item_key ],
214241 parent_keys = current_keys ,
215242 )
@@ -293,13 +320,13 @@ def _assign_default_values(self, schema, config):
293320 if not config_value :
294321 config_value = config [schema_item_key ] = {}
295322
296- property_schema = self ._get_object_property_schema (
323+ properties_schema = self ._get_object_properties_schema (
297324 schema_item ,
298- additional_properties_keys = config_value .keys (),
325+ objecy_keys = config_value .keys (),
299326 )
300327
301328 self ._assign_default_values (
302- schema = property_schema , config = config_value
329+ schema = properties_schema , config = config_value
303330 )
304331 elif schema_item_type == "array" :
305332 has_items = schema_item .get ("items" , None )
0 commit comments