OpenAPITools / OpenAPITools/openapi-generator

[BUG] [Python] OneOf objects can't be created if they don't have required properties

Open
#22,104 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Bug Report Checklist
  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

When using a oneOf, the class generated for that property will have methods to create an instance from a dict or json. When using it, it will try to create an object for each class in the oneOf, and will give an error if either no objects could be generated or if two or more objects were generated.
My issue is that this causes a problem when inside the oneOf there are objects with no required properties, because any single dictionary will pass the validations of each oneOf object, resulting in an error since two or more objects were generated. I have additionalProperties set as True for both oneOf objects, but the current implementation of the generated classes still allows the validation to pass even if the dict object has fields that have no correspondence in that class.

openapi-generator version

7.16.0

OpenAPI declaration file content or url
components:
  schemas:
    foo:
      oneOf:
        - title: Bar1
          additionalProperties: false
          properties:
            bar1:
              type: string
          type: object
        - title: Bar2
          additionalProperties: false
          properties:
            bar2:
              type: string
          type: object
info:
  title: Title
  version: 1.0.0
openapi: 3.0.4
paths: {}
Generation Details

Command:
openapi-generator-cli generate -i api.yaml -g python --additional-properties=generateSourceCodeOnly=true --package-name generated-api

Steps to reproduce
  • Save the yaml above in a file called api.yaml
  • Run the command above to generate the code
  • Run the following python code to observe the issues (you might need to install pydantic):
from generated_api.models.bar2 import Bar2
from generated_api.models.foo import Foo

try:
    Bar2.from_json('{"bar1": "bar"}')
    print("BUG: Bar2 accepted unknown field!")
except Exception as e:
    print(f"Expected: {e}")

try:
    foo = Foo.from_json('{"bar2": "bar"}')
    print(f"Success: Foo is of type {type(foo)}")
except Exception as e:
    print(f"BUG: {e}")

And the output should be something like:

BUG: FooOneOf accepted unknown field!
BUG: Multiple matches found when deserializing the JSON string into Foo with oneOf schemas: Bar1, Bar2. Details: 
Suggest a fix

The code for the oneOf classes to create an object from a dict is the following:

@classmethod
    def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
        """Create an instance of Bar2 from a dict"""
        if obj is None:
            return None

        if not isinstance(obj, dict):
            return cls.model_validate(obj)

        _obj = cls.model_validate({
            "bar2": obj.get("bar2")
        })
        return _obj

I tested changing it to

@classmethod
    def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
        """Create an instance of Bar2 from a dict"""
        if obj is None:
            return None

        return cls.model_validate(obj)

As well as adding extra="forbid" to the model_config.

The two changes together fixed my issues. The problem with the original function is that it only fetches the correct fields from each object, which would ignore the extra fields, guaranteeing the creation of a correct object, but removing its function to validate the object.
Adding extra="forbid" here is essential, especially because I have additionalProperties as True in the api.yaml

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the case with api.yaml using openapi-generator-cli and inspect generated_api/models/bar2.py and foo.py, especially their from_dict entry points. Compare the generated behavior with the expected oneOf selection and unknown-field validation shown in the report. Done means the supplied Python example rejects Bar2's unknown field and deserializes Foo without the multiple-match error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.