OpenAPITools / OpenAPITools/openapi-generator

[BUG][python] Explicit additionalProperties: false ignored when disallowAdditionalPropertiesIfNotPresent=false

Open
#24,329 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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
Description

In the python generator, an explicit per-schema additionalProperties: false in the spec is silently ignored when the global option disallowAdditionalPropertiesIfNotPresent=false is set. The unknown-field rejection loop in the generated model's from_dict is keyed solely off the global flag, never off the per-schema spec value.

The documented semantics of the flag say the opposite should happen:

If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications.

Per OAS / JSON Schema semantics, the flag should only fill in behavior when the spec is silent about additionalProperties. An explicit additionalProperties: false should always produce strict behavior regardless of the flag. As it stands, users who set disallowAdditionalPropertiesIfNotPresent=false (to get spec-compliant defaults) lose all strictness, including strictness the spec explicitly asks for.

Verified matrix (openapi-generator-cli v7.23.0, python generator, usePydanticV2=true):

spec declares additionalProperties: false on the schema disallowAdditionalPropertiesIfNotPresent rejection loop in generated from_dict
yes (OAS 3.1.0) false absent (bug)
yes (OAS 3.0.3) false absent (bug — not 3.1-specific)
yes (OAS 3.1.0) true present
no (OAS 3.1.0) true present (loop tracks only the flag)

In the failing rows there is no other strictness marker in the generated model either — no pydantic extra="forbid", nothing. The explicit additionalProperties: false is entirely lost.

openapi-generator version

7.23.0 (docker image openapitools/openapi-generator-cli:v7.23.0). Not a regression as far as I can tell — the template gate on master is unchanged, so the issue still exists on latest master:

https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/python/model_generic.mustache#L495-L503

        {{#disallowAdditionalPropertiesIfNotPresent}}
        {{^isAdditionalPropertiesTrue}}
        # raise errors for additional fields in the input
        for _key in obj.keys():
            if _key not in cls.__properties:
                raise ValueError("Error due to additional fields (not defined in {{classname}}) in the input: " + _key)

        {{/isAdditionalPropertiesTrue}}
        {{/disallowAdditionalPropertiesIfNotPresent}}

The outer section is the global CLI option, so the loop can never be emitted for an individual schema when the option is false, no matter what the schema says.

OpenAPI declaration file content or url
{
  "openapi": "3.1.0",
  "info": {"title": "Repro", "version": "1.0.0"},
  "paths": {
    "/things": {
      "post": {
        "operationId": "createThing",
        "requestBody": {
          "required": true,
          "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Payload"}}}
        },
        "responses": {"204": {"description": "ok"}}
      }
    }
  },
  "components": {
    "schemas": {
      "Payload": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name"],
        "properties": {
          "name": {"type": "string"},
          "count": {"type": "integer"}
        }
      }
    }
  }
}

(Same result with "openapi": "3.0.3".)

Generation Details
docker run --rm -v $PWD:/work openapitools/openapi-generator-cli:v7.23.0 generate \
  -g python -i /work/spec.json -o /work/out \
  --additional-properties packageName=repro,usePydanticV2=true,disallowAdditionalPropertiesIfNotPresent=false,hideGenerationTimestamp=true
Steps to reproduce
  1. Save the spec above as spec.json.
  2. Run the command above.
  3. Inspect out/repro/models/payload.py, from_dict.

Actual output (disallowAdditionalPropertiesIfNotPresent=false, spec explicitly forbids additional properties):

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

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

        _obj = cls.model_validate({
            "name": obj.get("name"),
            "count": obj.get("count")
        })
        return _obj

Payload.from_dict({"name": "x", "unexpected": 1}) succeeds and drops the unknown key, even though the spec declares additionalProperties: false.

Expected output (what the same schema produces when the flag is true):

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

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

        # raise errors for additional fields in the input
        for _key in obj.keys():
            if _key not in cls.__properties:
                raise ValueError("Error due to additional fields (not defined in Payload) in the input: " + _key)

        _obj = cls.model_validate({
            "name": obj.get("name"),
            "count": obj.get("count")
        })
        return _obj

Expected behavior: the rejection loop (or equivalent, e.g. pydantic extra="forbid") should be emitted whenever the schema explicitly declares additionalProperties: false, regardless of disallowAdditionalPropertiesIfNotPresent. The flag should only decide the behavior for schemas that don't mention additionalProperties at all.

Related issues/PRs
  • #13142 reports the analogous confusion for the C# generator (disallowAdditionalPropertiesIfNotPresent=false vs explicit spec values).
  • #21169 requests changing the flag's default to false — which would make this bug the default behavior for every explicit additionalProperties: false schema.
Suggest a fix

In model_generic.mustache, the rejection loop is gated on the global {{#disallowAdditionalPropertiesIfNotPresent}} option. It should additionally be emitted when the model's schema has an explicit additionalProperties: false (e.g. gate on a per-model property such as isAdditionalPropertiesTrue being false because the spec said so, which likely requires the codegen to distinguish "explicit false" from "absent" — CodegenModel/DefaultCodegen already track isAdditionalPropertiesTrue, but there is currently no per-model "explicitly false" signal exposed to the template).

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

Start with modules/openapi-generator/src/main/resources/python/model_generic.mustache and trace isAdditionalPropertiesTrue through CodegenModel and DefaultCodegen. Run the reported Python generator reproduction with both flag settings, then verify that explicit additionalProperties: false emits rejection behavior while schemas that omit it still follow the global option.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, python
Domain
api, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.