OpenAPITools / OpenAPITools/openapi-generator

[BUG] [Python] Cyclic Dependencies in Python Generator

Open
#3,171 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug Server: Python
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 (example)?
  • What's the version of OpenAPI Generator used?
  • Have you search for related issues/PRs?
  • What's the actual output vs expected output?
Description

The generated models do not work in python because the import statement is build the wrong way.
To fully fix the issue you also need #2978.

openapi-generator version
4.0.0 stable
OpenAPI declaration file content or url
---
components:
  schemas:
    Group:
      type: object
      description: "A simple description this is a group"
      properties:
        groupId:
          format: int32
          type: integer
        groupName:
          type: string
        groupDescription:
          type: string
        scope:
          format: int32
          type: integer
        members:
          type: array
          items:
            $ref: "#/components/schemas/User"
    User:
      type: object
      description: "Simple Description this is a User"
      properties:
        userId:
          format: int32
          type: integer
        userName:
          type: string
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
        groups:
          type: array
          items:
            $ref: "#/components/schemas/Group"
Command line used for generation
java -DsupportingFiles=util.py,__init__.py,base_model_.py,typing_utils.py,openapi.yaml,schemas.py -Dmodels -DskipFormModel=true -jar /usr/share/java/openapi-generator-cli-4.0.0.jar generate -i ../openapi-v3.yaml -g python-aiohttp -o ./out/python --skip-validate-spec -c ../config.json --model-package dataobjects
Steps to reproduce
  1. First build the objects.
  2. Create a Package from them.
  3. After trying to import either Group or Userm, it will fail because of the way python handles importing classes with the following syntax:
# change
from model_package.dataobjects.group import Group
# to
import model_package.dataobjects.group
# no more cyclic dependencies
Suggest a fix
    @Override
    public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
        Map<String, Object> result = super.postProcessAllModels(objs);
        for (Map.Entry<String, Object> entry : result.entrySet()) {
            Map<String, Object> inner = (Map<String, Object>) entry.getValue();
            List<Map<String, Object>> models = (List<Map<String, Object>>) inner.get("models");
            for (Map<String, Object> mo : models) {
                CodegenModel cm = (CodegenModel) mo.get("model");
                // Add additional filename information for imports
                mo.put("pyImports", toPyImports(cm, cm.imports));
            }
        }
        return result;
    }
    @Override
    public String toModelImport(String name) {
        String modelImport;
        if (StringUtils.startsWithAny(name, "import", "from")) {
            modelImport = name;
        } else {
            modelImport = "import ";
            if (!"".equals(modelPackage())) {
                modelImport += modelPackage() + ".";
            }
            modelImport += toModelFilename(name);
        }
        return modelImport;
    }
    private List<Map<String, String>> toPyImports(CodegenModel cm, Set<String> imports) {
        List<Map<String, String>> pyImports = new ArrayList<>();
        for (String im : imports) {
            if (!im.equals(cm.classname)) {
                HashMap<String, String> pyImport = new HashMap<>();
                pyImport.put("import", toModelImport(im));
                pyImports.add(pyImport);
            }
        }
        return pyImports;
    }
    @Override
    public String getTypeDeclaration(Schema p) {
      if (ModelUtils.isArraySchema(p)) {
          ArraySchema ap = (ArraySchema) p;
          Schema inner = ap.getItems();
          return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]";
      } else if (ModelUtils.isMapSchema(p)) {
          Schema inner = ModelUtils.getAdditionalProperties(p);
          return getSchemaType(p) + "[str, " + getTypeDeclaration(inner) + "]";
      } else if (ModelUtils.isComposedSchema(p)) {
        if (ModelUtils.getInterfaces((ComposedSchema)p) != null) {
            // quickfix for oneOf in python
            return "object";
        }
      }
      String type = super.getTypeDeclaration(p);
      if (languageSpecificPrimitives.contains(type)) {
          return type;
      }
      return "locate(\'" + this.toModelFilename(type)+ "." + type + "\')";
  }
# coding: utf-8

from datetime import date, datetime

from typing import List, Dict, Type

from {{modelPackage}}.base_model_ import Model
{{#models}}
{{#model}}
{{#pyImports}}
{{import}}
{{/pyImports}}
{{/model}}
{{/models}}
from {{packageName}} import util
from pydoc import locate
....

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 the Python generator entry points postProcessAllModels, toModelImport, and getTypeDeclaration, then inspect the model-generation Mustache template shown in the issue. Reproduce the Group/User schema cycle with the provided command and compare the generated imports. Done means the generated models can be imported without the cyclic dependency failure, while also accounting for related issue #2978.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.