OpenAPITools / OpenAPITools/openapi-generator
[BUG][python] Explicit additionalProperties: false ignored when disallowAdditionalPropertiesIfNotPresent=false
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:
{{#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
- Save the spec above as
spec.json. - Run the command above.
- 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=falsevs explicit spec values). - #21169 requests changing the flag's default to
false— which would make this bug the default behavior for every explicitadditionalProperties: falseschema.
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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