Skip to content

Conversation

@ericfitz
Copy link

@ericfitz ericfitz commented Dec 29, 2025

Summary

When a child model extends a parent via allOf, the generated __init__ method was consuming parameters from kwargs, preventing them from reaching parent classes. This caused deserialization failures with errors like:

  • ValueError: Invalid value for 'name' (None), must not be 'None'
  • TypeError: Parent.__init__() got an unexpected keyword argument 'child_field'

The Problem

Consider this OpenAPI spec with inheritance:

components:
  schemas:
    Parent:
      type: object
      required: [name, type]
      properties:
        name:
          type: string
        type:
          type: string
          enum: [typeA, typeB]
    
    Child:
      allOf:
        - $ref: '#/components/schemas/Parent'
        - type: object
          properties:
            child_field:
              type: string

The old generated code had two problems:

class Child(Parent):
    def __init__(self, name=None, type=None, child_field=None, *args, **kwargs):
        self._name = None
        self._type = None
        self._child_field = None
        self.name = name          # consumed from kwargs
        self.type = type          # consumed from kwargs  
        self.child_field = child_field
        Parent.__init__(self, *args, **kwargs)  # BUG: name, type NOT in kwargs anymore!

class Parent:
    def __init__(self, name=None, type=None):  # No **kwargs - rejects unknown params!
        self.name = name  # Gets None because Child consumed it
        self.type = type  # Gets None because Child consumed it

Problem 1: Python extracts named parameters from **kwargs into local variables, so when Parent.__init__ is called, those parameters are no longer in kwargs.

Problem 2: Root classes didn't accept **kwargs, so if a child class has fields the parent doesn't know about, it fails with "unexpected keyword argument".

The Fix

For child classes (with parent): Use **kwargs for all parameters and extract with kwargs.get():

class Child(Parent):
    def __init__(self, **kwargs):
        self._name = None
        self._type = None
        self._child_field = None
        name = kwargs.get('name')
        type = kwargs.get('type')
        child_field = kwargs.get('child_field')
        self.name = name
        self.type = type
        self.child_field = child_field
        Parent.__init__(self, **kwargs)  # Full kwargs passed through!

For root classes (without parent): Add **kwargs to accept extra parameters:

class Parent:
    def __init__(self, name=None, type=None, **kwargs):  # Accepts extra kwargs
        self.name = name
        self.type = type

This ensures the full kwargs dict flows through the entire inheritance chain, and each class extracts what it needs without breaking the chain.

Test Plan

  • Generated a Python client with multi-level allOf inheritance
  • Verified child class instances correctly populate parent class attributes
  • Verified parent classes correctly ignore unknown child-specific fields
  • Tested deserialization of API responses with inherited models
  • In my project: all unit tests pass
  • In my project: all integration tests pass (against production API)

Diagnosis Notes

This bug manifests as errors like:

  • ValueError: Invalid value for 'property_name' (None), must not be 'None'
  • ValueError: Invalid value for 'property_name' (None), must be one of [...]
  • Parent class validation failures during deserialization of child objects

Repro Notes

  • My OpenAPI schema is at here
  • My OpenAPI client generation code is here
  • I generated my client with swagger-codegen 3.0.75
  • Tested with Python 3.14.2

🤖 Generated with Claude Code
🧍🏻Manually reviewed and edited by a real human, not just AI slop

…ce chain

When a child model extends a parent via allOf, the generated __init__
method was consuming parameters from kwargs, preventing them from
reaching parent classes. This caused deserialization failures.

The fix changes how inheritance is handled:

1. Child classes (with parent) now use **kwargs for all parameters:
   - Extract values with kwargs.get('name')
   - Pass the full **kwargs to parent (nothing consumed/removed)

2. Root classes (without parent) now accept **kwargs:
   - Keep named parameters for clean API
   - Add **kwargs to absorb extra parameters from child classes

Before (broken):
```python
class Child(Parent):
    def __init__(self, name=None, type=None, child_only=None, *args, **kwargs):
        self.name = name  # consumed from kwargs
        self.type = type  # consumed from kwargs
        Parent.__init__(self, *args, **kwargs)  # name, type NOT passed!
```

After (fixed):
```python
class Child(Parent):
    def __init__(self, **kwargs):
        name = kwargs.get('name')
        type = kwargs.get('type')
        child_only = kwargs.get('child_only')
        self.name = name
        self.type = type
        Parent.__init__(self, **kwargs)  # full kwargs passed!

class Parent:
    def __init__(self, name=None, type=None, **kwargs):  # accepts extra kwargs
        self.name = name
        self.type = type
```

This ensures kwargs flows through the entire inheritance chain.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ericfitz ericfitz marked this pull request as draft December 29, 2025 05:57
@ericfitz ericfitz marked this pull request as ready for review December 29, 2025 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant