datamodel-code-generator
datamodel-code-generator copied to clipboard
"A Parser can not resolve classes" error when generating code from an OpenAPI spec
Hey all, first of all thank you all for coming up with this project and the continued work on it.
I bumped into the following crash this morning. I may attempt to investigate what's wrong but I figured it wouldn't hurt to report it right away.
Describe the bug
datamodel-codegen produced an error instead of generating code:
% wget https://raw.githubusercontent.com/lune-climate/lune-docs/68726ac5b6e3b5c76cb683503311f0bb39d302f4/static/openapi.yml
--2024-05-22 14:33:42-- https://raw.githubusercontent.com/lune-climate/lune-docs/68726ac5b6e3b5c76cb683503311f0bb39d302f4/static/openapi.yml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 292341 (285K) [text/plain]
Saving to: ‘openapi.yml’
openapi.yml 100%[======================================================================================================================>] 285.49K --.-KB/s in 0.1s
2024-05-22 14:33:42 (2.20 MB/s) - ‘openapi.yml’ saved [292341/292341]
% datamodel-codegen --input openapi.yml --input-file-type openapi --output models.py
Traceback (most recent call last):
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/__main__.py", line 447, in main
generate(
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/__init__.py", line 468, in generate
results = parser.parse()
^^^^^^^^^^^^^^
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/parser/base.py", line 1187, in parse
_, sorted_data_models, require_update_action_models = sort_data_models(
^^^^^^^^^^^^^^^^^
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/parser/base.py", line 141, in sort_data_models
return sort_data_models(
^^^^^^^^^^^^^^^^^
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/parser/base.py", line 141, in sort_data_models
return sort_data_models(
^^^^^^^^^^^^^^^^^
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/parser/base.py", line 141, in sort_data_models
return sort_data_models(
^^^^^^^^^^^^^^^^^
[Previous line repeated 1 more time]
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/parser/base.py", line 208, in sort_data_models
raise Exception(f'A Parser can not resolve classes: {unresolved_classes}.')
Exception: A Parser can not resolve classes: [class: openapi.yml#/components/schemas/OrderQuoteByQuantityRequest references: frozenset({'openapi.yml#/components/schemas/OrderQuoteByQuantityWithBundleMass', 'openapi.yml#/components/schemas/OrderQuoteByQuantityWithBundlePercentage'})], [class: openapi.yml#/components/schemas/OrderQuoteByQuantityWithBundlePercentage references: frozenset({'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc', 'openapi.yml#/components/schemas/Mass'})], [class: openapi.yml#/components/schemas/OrderQuoteByQuantityWithBundleMass references: frozenset({'openapi.yml#/components/schemas/BundleMass', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/CreateOrderByQuantityRequest references: frozenset({'openapi.yml#/components/schemas/CreateOrderByQuantityWithBundleMass', 'openapi.yml#/components/schemas/CreateOrderByQuantityWithBundlePercentage'})], [class: openapi.yml#/components/schemas/CreateOrderByQuantityWithBundlePercentage references: frozenset({'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc', 'openapi.yml#/components/schemas/Mass'})], [class: openapi.yml#/components/schemas/CreateOrderByQuantityWithBundleMass references: frozenset({'openapi.yml#/components/schemas/BundleMass', 'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/OrderQuoteByValueRequest references: frozenset({'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/CreateOrderByValueRequest references: frozenset({'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc', 'openapi.yml#/components/schemas/Metadata'})], [class: openapi.yml#/components/schemas/CreateOrderByEstimateRequest references: frozenset({'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc', 'openapi.yml#/components/schemas/Metadata'})], [class: openapi.yml#/components/schemas/ElectricityEstimateRequest references: frozenset({'openapi.yml#/components/schemas/EstimateIdempotencyKey', 'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/ElectricityConsumption', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/FlightEstimateRequest references: frozenset({'openapi.yml#/components/schemas/Distance', 'openapi.yml#/components/schemas/CabinClass', 'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/AirportSourceDestination', 'openapi.yml#/components/schemas/EstimateIdempotencyKey', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/PassengerTransportationEstimateRequest references: frozenset({'openapi.yml#/components/schemas/PassengerFlightEstimateRequest', 'openapi.yml#/components/schemas/PassengerRoadEstimateRequest', 'openapi.yml#/components/schemas/PassengerRailEstimateRequest', 'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/EstimateIdempotencyKey', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/PassengerTransportationEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/EstimateQuote', 'openapi.yml#/components/schemas/PassengerTransportationEstimateRequest', 'openapi.yml#/components/schemas/EmissionEstimate'})], [class: openapi.yml#/components/schemas/ShippingEstimateRequest#-datamodel-code-generator-#-allOf-#-special-# references: frozenset({'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/EstimateIdempotencyKey', 'openapi.yml#/components/schemas/QuantityTrunc', 'openapi.yml#/components/schemas/ShippedAt', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/Shipment'})], [class: openapi.yml#/components/schemas/ShippingEstimateRequest references: frozenset({'openapi.yml#/components/schemas/ShippingEstimateRequest#-datamodel-code-generator-#-union_model-1-#-special-#', 'openapi.yml#/components/schemas/ShippingEstimateRequest#-datamodel-code-generator-#-union_model-0-#-special-#'})], [class: openapi.yml#/components/schemas/MultiLegShippingEstimateRequest references: frozenset({'openapi.yml#/components/schemas/ShippedAt', 'openapi.yml#/components/schemas/Shipment', 'openapi.yml#/components/schemas/MultiLegShippingEstimateRequest/legs#-datamodel-code-generator-#-array-#-special-#/0#-datamodel-code-generator-#-oneOf-#-special-#/0#-datamodel-code-generator-#-object-#-special-#', 'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/EstimateIdempotencyKey', 'openapi.yml#/components/schemas/MultiLegShippingEstimateRequest/legs#-datamodel-code-generator-#-array-#-special-#/0#-datamodel-code-generator-#-oneOf-#-special-#/1#-datamodel-code-generator-#-object-#-special-#', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/BatchTransactionEstimateRequest references: frozenset({'openapi.yml#/components/schemas/TransactionEstimateRequest'})], [class: openapi.yml#/components/schemas/TransactionEstimateRequest references: frozenset({'openapi.yml#/components/schemas/Merchant', 'openapi.yml#/components/schemas/Diet', 'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/EstimateIdempotencyKey', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/MonetaryAmount', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/CompanyEstimateRequest references: frozenset({'openapi.yml#/components/schemas/IntegerPercentage', 'openapi.yml#/components/schemas/CompanyEstimateRequest/tech#-datamodel-code-generator-#-object-#-special-#', 'openapi.yml#/components/schemas/Distance', 'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/EstimateIdempotencyKey', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/MonetaryAmount', 'openapi.yml#/components/schemas/QuantityTrunc', 'openapi.yml#/components/schemas/Area'})], [class: openapi.yml#/components/schemas/ElectricityEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/EmissionEstimateResponse', 'openapi.yml#/components/schemas/ElectricityEstimateRequest'})], [class: openapi.yml#/components/schemas/FlightEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/EmissionEstimateResponse', 'openapi.yml#/components/schemas/FlightEstimateRequest'})], [class: openapi.yml#/components/schemas/BatchTransactionEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/EmptyObject', 'openapi.yml#/components/schemas/TransactionEmissionEstimate', 'openapi.yml#/components/schemas/ErrorResponse'})], [class: openapi.yml#/components/schemas/TransactionEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/EmissionEstimateResponse', 'openapi.yml#/components/schemas/EmissionFactorWithGasEmissions', 'openapi.yml#/components/schemas/NullEnum', 'openapi.yml#/components/schemas/TransactionEstimateRequest'})], [class: openapi.yml#/components/schemas/SingleShippingEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/ShippingEstimateRequest', 'openapi.yml#/components/schemas/EstimateQuote', 'openapi.yml#/components/schemas/ShippedAt', 'openapi.yml#/components/schemas/ShippingLegEmissionEstimate'})], [class: openapi.yml#/components/schemas/MultiLegShippingEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/Distance', 'openapi.yml#/components/schemas/AdjustedDistance', 'openapi.yml#/components/schemas/EmissionEstimateResponse', 'openapi.yml#/components/schemas/MultiLegShippingEstimateRequest', 'openapi.yml#/components/schemas/ShippedAt', 'openapi.yml#/components/schemas/ShippingLegEmissionEstimate'})], [class: openapi.yml#/components/schemas/AnyShippingEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/MultiLegShippingEmissionEstimate', 'openapi.yml#/components/schemas/SingleShippingEmissionEstimate'})], [class: openapi.yml#/components/schemas/PaginatedAnyShippingEmissionEstimates references: frozenset({'openapi.yml#/components/schemas/PaginatedBase', 'openapi.yml#/components/schemas/AnyShippingEmissionEstimate'})], [class: openapi.yml#/components/schemas/CompanyEmissionEstimate references: frozenset({'openapi.yml#/components/schemas/EmissionEstimateResponse', 'openapi.yml#/components/schemas/Mass', 'openapi.yml#/components/schemas/CompanyEstimateRequest', 'openapi.yml#/components/schemas/CompanyEmissionEstimate/components#-datamodel-code-generator-#-object-#-special-#'})], [class: openapi.yml#/components/schemas/EmissionFactorEstimateRequest references: frozenset({'openapi.yml#/components/schemas/EmissionFactorActivity', 'openapi.yml#/components/schemas/EstimateIdempotencyKey', 'openapi.yml#/components/schemas/Metadata', 'openapi.yml#/components/schemas/BundleSelectionRequest', 'openapi.yml#/components/schemas/QuantityTrunc'})], [class: openapi.yml#/components/schemas/EmissionFactorEstimate references: frozenset({'openapi.yml#/components/schemas/EmissionEstimateResponse', 'openapi.yml#/components/schemas/EmissionFactorEstimateRequest', 'openapi.yml#/components/schemas/EmissionFactorWithGasEmissions'})], [class: openapi.yml#/components/schemas/ShippingEstimateRequest#-datamodel-code-generator-#-union_model-0-#-special-# references: frozenset({'openapi.yml#/components/schemas/ShippingEstimateRequest/0#-datamodel-code-generator-#-object-#-special-#', 'openapi.yml#/components/schemas/ShippingEstimateRequest#-datamodel-code-generator-#-allOf-#-special-#'})], [class: openapi.yml#/components/schemas/ShippingEstimateRequest#-datamodel-code-generator-#-union_model-1-#-special-# references: frozenset({'openapi.yml#/components/schemas/ShippingEstimateRequest#-datamodel-code-generator-#-allOf-#-special-#', 'openapi.yml#/components/schemas/ShippingEstimateRequest/1#-datamodel-code-generator-#-object-#-special-#'})].
To Reproduce
The OpenAPI schema used (too large to include): https://raw.githubusercontent.com/lune-climate/lune-docs/68726ac5b6e3b5c76cb683503311f0bb39d302f4/static/openapi.yml
There may be something non-standard or broken about this specification that I'm not aware of but it is accepted by multiple other libraries/tools and it's been working fine so far.
Used commandline:
$ datamodel-codegen --input openapi.yml --input-file-type openapi --output models.py
Expected behavior I'd expect one of the following:
- An error message explaining some OpenAPI feature is used that is currently unsupported
- Code successfully generated
Version:
- OS: macOS 13.4
- Python version: 3.12.3
- datamodel-code-generator version: 0.25.6
% pip freeze
annotated-types==0.7.0
argcomplete==3.3.0
black==24.4.2
click==8.1.7
coverage==7.5.1
datamodel-code-generator==0.25.6
dnspython==2.6.1
email_validator==2.1.1
genson==1.3.0
idna==3.7
inflect==5.6.2
iniconfig==2.0.0
isort==5.13.2
Jinja2==3.1.4
MarkupSafe==2.1.5
mypy==1.10.0
mypy-extensions==1.0.0
packaging==24.0
pathspec==0.12.1
platformdirs==4.2.2
pluggy==1.5.0
pydantic==2.7.1
pydantic_core==2.18.2
pytest==8.2.1
pytest-cov==5.0.0
PyYAML==6.0.1
ruff==0.4.4
typing_extensions==4.11.0
Additional context N/A
I managed to narrow it down to an input like this (openapi.yml):
openapi: 3.0.1
components:
schemas:
CreateOrderByEstimateRequest:
type: object
properties:
quantity_trunc:
$ref: '#/components/schemas/QuantityTrunc'
QuantityTrunc:
type: string
description: Selects to which precision to truncate quantities specific to carbon offsetting.
example: 't'
allOf:
- $ref: '#/components/schemas/MassUnit'
MassUnit:
type: string
enum:
- g
- kg
- t
The crash log:
% poetry run datamodel-codegen --input openapi.yml --input-file-type openapi --output models.py
Traceback (most recent call last):
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/__main__.py", line 447, in main
generate(
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/__init__.py", line 468, in generate
results = parser.parse()
^^^^^^^^^^^^^^
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/parser/base.py", line 1187, in parse
_, sorted_data_models, require_update_action_models = sort_data_models(
^^^^^^^^^^^^^^^^^
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/parser/base.py", line 141, in sort_data_models
return sort_data_models(
^^^^^^^^^^^^^^^^^
File "/Users/user/Library/Caches/pypoetry/virtualenvs/spreadsheet-offset-tool-L93AmRO5-py3.12/lib/python3.12/site-packages/datamodel_code_generator/parser/base.py", line 208, in sort_data_models
raise Exception(f'A Parser can not resolve classes: {unresolved_classes}.')
Exception: A Parser can not resolve classes: [class: openapi.yml#/components/schemas/CreateOrderByEstimateRequest references: frozenset({'openapi.yml#/components/schemas/QuantityTrunc'})].
I can't reduce the example any further.
- If I remove the
enumblock fromMassUnitthe crash goes away - If I remove the
CreateOrderByEstimateRequesttype the crash goes away - If I remove
QuantityTruncand makeCreateOrderByEstimateRequestrefer toMassUnit(instead ofQuantityTrunc) the crash goes away
So it seems like a $ref/anyOf/enum edge case?
Interesting, if I change that allOf instance to oneOf (possible in this case, only one child type anyway), like
openapi: 3.0.1
components:
schemas:
CreateOrderByEstimateRequest:
type: object
properties:
quantity_trunc:
$ref: '#/components/schemas/QuantityTrunc'
QuantityTrunc:
type: string
description: Selects to which precision to truncate quantities specific to carbon offsetting.
example: 't'
oneOf:
- $ref: '#/components/schemas/MassUnit'
MassUnit:
type: string
enum:
- g
- kg
- t
the tool actually generates code:
# generated by datamodel-codegen:
# filename: openapi.yml
# timestamp: 2024-05-23T11:41:39+00:00
from __future__ import annotations
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
class MassUnit(Enum):
g = 'g'
kg = 'kg'
t = 't'
class QuantityTrunc(BaseModel):
__root__: MassUnit = Field(
...,
description='Selects to which precision to truncate quantities specific to carbon offsetting.',
example='t',
)
class CreateOrderByEstimateRequest(BaseModel):
quantity_trunc: Optional[QuantityTrunc] = None
Hi. I'm also facing this issue. Did you find a way to resolve it other than replacing allOf with oneOf?
Hey @dpeachey, no, I had to get things done quickly so because of this and some other issues I skipped code generation completely and just wrote the types I needed by hand. :|