Skip to content

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

Description

@ahridin

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
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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions