@@ -395,9 +395,53 @@ def get_data_type(self, obj: JsonSchemaObject) -> DataType:
395395
396396 return super ().get_data_type (obj )
397397
398+ def _normalize_discriminator_mapping_ref (self , mapping_value : str ) -> str : # noqa: PLR6301
399+ """Normalize a discriminator mapping value to a full $ref path.
400+
401+ Per OpenAPI spec, mapping values can be either:
402+ - Full refs: "#/components/schemas/Pet" or "./other.yaml#/components/schemas/Pet"
403+ - Short names: "Pet" or "Pet.V1" (relative to #/components/schemas/)
404+ - Relative paths: "schemas/Pet" or "./other.yaml"
405+
406+ Values containing "/" or "#" are treated as paths/refs and passed through.
407+ All other values (including those with dots like "Pet.V1") are treated as
408+ short schema names and normalized to full refs.
409+
410+ Note: Bare file references without path separators (e.g., "other.yaml") will be
411+ treated as schema names. Use "./other.yaml" format for file references.
412+
413+ Note: This could be a staticmethod, but @snooper_to_methods() decorator
414+ converts staticmethods to regular functions when pysnooper is installed.
415+ """
416+ if "/" in mapping_value or "#" in mapping_value :
417+ return mapping_value
418+ return f"#/components/schemas/{ mapping_value } "
419+
420+ def _normalize_discriminator (self , discriminator : dict [str , Any ]) -> dict [str , Any ]:
421+ """Return a copy of the discriminator dict with normalized mapping refs."""
422+ result = discriminator .copy ()
423+ mapping = discriminator .get ("mapping" )
424+ if mapping :
425+ result ["mapping" ] = {
426+ k : self ._normalize_discriminator_mapping_ref (v ) for k , v in mapping .items () if isinstance (v , str )
427+ }
428+ return result
429+
398430 def _get_discriminator_union_type (self , ref : str ) -> DataType | None :
399- """Create a union type for discriminator subtypes if available."""
431+ """Create a union type for discriminator subtypes if available.
432+
433+ First tries to use allOf subtypes. If none found, falls back to using
434+ the discriminator mapping to create the union type. This handles cases
435+ where schemas don't use allOf inheritance but have explicit discriminator mappings.
436+ """
400437 subtypes = self ._discriminator_subtypes .get (ref , [])
438+ if not subtypes :
439+ discriminator = self ._discriminator_schemas [ref ]
440+ mapping = discriminator .get ("mapping" , {})
441+ if mapping :
442+ subtypes = [
443+ self ._normalize_discriminator_mapping_ref (v ) for v in mapping .values () if isinstance (v , str )
444+ ]
401445 if not subtypes :
402446 return None
403447 refs = map (self .model_resolver .add_ref , subtypes )
@@ -430,10 +474,11 @@ def parse_object_fields(
430474 and (discriminator := self ._discriminator_schemas .get (field .ref ))
431475 ):
432476 new_field_type = self ._get_discriminator_union_type (field .ref ) or field_obj .data_type
477+ normalized_discriminator = self ._normalize_discriminator (discriminator )
433478 field_obj = self .data_model_field_type (** { # noqa: PLW2901
434479 ** field_obj .__dict__ ,
435480 "data_type" : new_field_type ,
436- "extras" : {** field_obj .extras , "discriminator" : discriminator },
481+ "extras" : {** field_obj .extras , "discriminator" : normalized_discriminator },
437482 })
438483 result_fields .append (field_obj )
439484
0 commit comments