datamodel-code-generator icon indicating copy to clipboard operation
datamodel-code-generator copied to clipboard

Generates model containing name confusion with imported module

Open syntaxaire opened this issue 1 year ago • 3 comments

Describe the bug Code is generated that resembles:

from datetime import date

class ActivityBase(BaseModel):
    date: Optional[date] = Field(
        None, description="The date the Activity was performed (as a ISO-8601 date)"
    )

date: Optional[date] is a recursive reference that crashes under Pydantic 2.

To Reproduce

Example schema:

{
  "openapi": "3.0.0",
  "info": {
    "title": "API Documentation",
    "contact": {
      "name": "API Support",
      "email": "[email protected]"
    },
    "description": "API documentation",
    "version": "v4"
  },
  "components": {
    "schemas": {
      "Activity_base": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "The date the Activity was performed (as a ISO-8601 date)"
          }
        }
      }
    }
  }
}

Used commandline:

$ datamodel-codegen --use-standard-collections --use-schema-description --use-double-quotes --input-file-type openapi --target-python-version 3.10 --encoding utf8 --input openapi.json --output models.py

Importing models.py with Pydantic 2.1.1 will then crash with a lengthy stack trace ending in:

RecursionError: maximum recursion depth exceeded

Expected behavior The generated code could use namespaced imports to prevent conflicting with common names like 'date'. For example,

import datetime

class ActivityBase(BaseModel):
    date: Optional[datetime.date] = Field(
        None, description="The date the Activity was performed (as a ISO-8601 date)"
    )

explicitly identifies the intended reference (and does not crash).

Version:

  • OS: Windows 11
  • Python version: 3.11.0
  • datamodel-code-generator version: 0.21.1

syntaxaire avatar Jul 27 '23 01:07 syntaxaire

A similar problem arises, when we use the following schema

{
  "components": {
    "schemas": {
      "shared.RuntimeAudit": {
        "description": "Runtime ...",
        "properties": {
          "runtime": {
            "$ref": "#/components/schemas/runtime.RuleEffect"
          }
        }
      },
      "runtime.RuleEffect": {
        "description": "Some rule effect",
        "enum": ["block", "prevent", "alert", "disable"],
        "type": "string"
      }
    }
  }
}

That will generate a pydantic v2 model like this (the shared.py file):

from __future__ import annotations

from pydantic import BaseModel

from . import runtime


class RuntimeAudit(BaseModel):
    runtime: runtime.RuleEffect | None = None

Importing this module will raise an AttributeError because pydantic v2 (and probably python) will be confused by the field having the same name as the module.

AttributeError: 'NoneType' object has no attribute 'RuleEffect'

Would it be possible to use an alias for the module, if a naming conflict is detected? For example

from . import runtime as rt

class RuntimeAudit(BaseModel):
    runtime: rt.RuleEffect | None = None

FSchumacher avatar Aug 01 '23 11:08 FSchumacher

Any update on if there's any plan to fix this bug?

We are having the exact same issue with a field named date and having the format set to "date". We're using it in openapi-to-fastapi which generates FastAPI routes on the fly from OpenAPI specs, which means we don't really have the option to manually edit the outputted pydantic models.

I guess I'll need to see if I can figure out some work around or if I'm able to somehow fix the issue in datamodel-code-generator myself. If anyone has any good suggestions on any work around (that does not involve renaming the field and work dynamically for files with arbitrary names etc) I'd be thankful. Or if there's any pointers to how it could be fixed in datamodel-code-generator that could also be helpful.

joakimnordling avatar Jan 03 '24 14:01 joakimnordling

I'm hitting a similar issue; this is a simplified reproduction with the following input jsonschema:

foo.json

{
  "$id": "foo.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Foo",
  "type": "object",
  "properties": {
    "bar": {
      "$ref": "bar.json"
    }
  }
}

bar.json

{
  "$id": "bar.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Bar",
  "type": "object"
}

I ran this to generate pydantic models:

datamodel-codegen --input mydir/ --output-model-type pydantic_v2.BaseModel --output mydir/

This generates the following:

foo.py

from __future__ import annotations

from typing import Optional

from pydantic import BaseModel

from . import bar


class Foo(BaseModel):
    bar: Optional[bar.Bar] = None

bar.py

from __future__ import annotations

from pydantic import BaseModel


class Bar(BaseModel):
    pass

When trying to import Foo, this results in an AttributeError as above:

AttributeError: 'NoneType' object has no attribute 'Bar'

Edit

To add to this, the following Pydantic issue gives a bit more context: https://github.com/pydantic/pydantic/issues/7554

For the example above, one workaround is to use an alias:

aliases.json

{ "bar": "bar_" }

Running with the following:

datamodel-codegen --input mydir/ --output-model-type pydantic_v2.BaseModel --output mydir/ --aliases aliases.json

This then changes Foo to the following:

class Foo(BaseModel):
    bar_: Optional[bar.Bar] = Field(None, alias='bar')

iain-palmer avatar Jan 26 '24 21:01 iain-palmer