Skip to content

add backward compatibility for exclusiveMinimum and exclusiveMaximum #1092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
default: patch
---

# Allow OpenAPI 3.1-style `exclusiveMinimum` and `exclusiveMaximum`

Fixed by PR #1092. Thanks @mikkelam!
31 changes: 29 additions & 2 deletions openapi_python_client/schema/openapi_schema_pydantic/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class Schema(BaseModel):
title: Optional[str] = None
multipleOf: Optional[float] = Field(default=None, gt=0.0)
maximum: Optional[float] = None
exclusiveMaximum: Optional[bool] = None
exclusiveMaximum: Optional[Union[bool, float]] = None
minimum: Optional[float] = None
exclusiveMinimum: Optional[bool] = None
exclusiveMinimum: Optional[Union[bool, float]] = None
maxLength: Optional[int] = Field(default=None, ge=0)
minLength: Optional[int] = Field(default=None, ge=0)
pattern: Optional[str] = None
Expand Down Expand Up @@ -160,6 +160,33 @@ class Schema(BaseModel):
},
)

@model_validator(mode="after")
def handle_exclusive_min_max(self) -> "Schema":
"""
Convert exclusiveMinimum/exclusiveMaximum between OpenAPI v3.0 (bool) and v3.1 (numeric).
"""
# Handle exclusiveMinimum
if isinstance(self.exclusiveMinimum, bool) and self.minimum is not None:
if self.exclusiveMinimum:
self.exclusiveMinimum = self.minimum
self.minimum = None
else:
self.exclusiveMinimum = None
elif isinstance(self.exclusiveMinimum, float):
self.minimum = None

# Handle exclusiveMaximum
if isinstance(self.exclusiveMaximum, bool) and self.maximum is not None:
if self.exclusiveMaximum:
self.exclusiveMaximum = self.maximum
self.maximum = None
else:
self.exclusiveMaximum = None
elif isinstance(self.exclusiveMaximum, float):
self.maximum = None

return self

@model_validator(mode="after")
def handle_nullable(self) -> "Schema":
"""Convert the old 3.0 `nullable` property into the new 3.1 style"""
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ ignore = ["E501", "PLR0913"]

[tool.ruff.lint.per-file-ignores]
"openapi_python_client/cli.py" = ["B008"]
"tests/*" = ["PLR2004"]

[tool.coverage.run]
omit = ["openapi_python_client/templates/*"]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_bad_config():

result = runner.invoke(app, ["generate", f"--config={config_path}", f"--path={path}"])

assert result.exit_code == 2 # noqa: PLR2004
assert result.exit_code == 2
assert "Unable to parse config" in result.stdout


Expand Down
2 changes: 1 addition & 1 deletion tests/test_parser/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ def test__add_parameters_query_optionality(self, config):
endpoint=endpoint, data=data, schemas=Schemas(), parameters=Parameters(), config=config
)

assert len(endpoint.query_parameters) == 2, "Not all query params were added" # noqa: PLR2004
assert len(endpoint.query_parameters) == 2, "Not all query params were added"
for param in endpoint.query_parameters:
if param.name == "required":
assert param.required
Expand Down
8 changes: 4 additions & 4 deletions tests/test_parser/test_properties/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ def test_property_from_data_union(self, config):
)[0]

assert isinstance(response, UnionProperty)
assert len(response.inner_properties) == 2 # noqa: PLR2004
assert len(response.inner_properties) == 2

def test_property_from_data_list_of_types(self, config):
from openapi_python_client.parser.properties import Schemas, property_from_data
Expand All @@ -705,7 +705,7 @@ def test_property_from_data_list_of_types(self, config):
)[0]

assert isinstance(response, UnionProperty)
assert len(response.inner_properties) == 2 # noqa: PLR2004
assert len(response.inner_properties) == 2

def test_property_from_data_union_of_one_element(self, model_property_factory, config):
from openapi_python_client.parser.properties import Schemas, property_from_data
Expand Down Expand Up @@ -907,7 +907,7 @@ def test_retries_failing_properties_while_making_progress(self, mocker, config):
call("#/components/schemas/first"),
]
)
assert update_schemas_with_data.call_count == 3 # noqa: PLR2004
assert update_schemas_with_data.call_count == 3
assert result.errors == [PropertyError()]


Expand Down Expand Up @@ -1171,7 +1171,7 @@ def test_retries_failing_parameters_while_making_progress(self, mocker, config):
call("#/components/parameters/first"),
]
)
assert update_parameters_with_data.call_count == 3 # noqa: PLR2004
assert update_parameters_with_data.call_count == 3
assert result.errors == [ParameterError()]


Expand Down
36 changes: 36 additions & 0 deletions tests/test_schema/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,39 @@ def test_nullable_with_any_of():
def test_nullable_with_one_of():
schema = Schema.model_validate_json('{"oneOf": [{"type": "string"}], "nullable": true}')
assert schema.oneOf == [Schema(type=DataType.STRING), Schema(type=DataType.NULL)]


def test_exclusive_minimum_as_boolean():
schema = Schema.model_validate_json('{"minimum": 10, "exclusiveMinimum": true}')
assert schema.exclusiveMinimum == 10
assert schema.minimum is None


def test_exclusive_maximum_as_boolean():
schema = Schema.model_validate_json('{"maximum": 100, "exclusiveMaximum": true}')
assert schema.exclusiveMaximum == 100
assert schema.maximum is None


def test_exclusive_minimum_as_number():
schema = Schema.model_validate_json('{"exclusiveMinimum": 5}')
assert schema.exclusiveMinimum == 5
assert schema.minimum is None


def test_exclusive_maximum_as_number():
schema = Schema.model_validate_json('{"exclusiveMaximum": 50}')
assert schema.exclusiveMaximum == 50
assert schema.maximum is None


def test_exclusive_minimum_as_false_boolean():
schema = Schema.model_validate_json('{"minimum": 10, "exclusiveMinimum": false}')
assert schema.exclusiveMinimum is None
assert schema.minimum == 10


def test_exclusive_maximum_as_false_boolean():
schema = Schema.model_validate_json('{"maximum": 100, "exclusiveMaximum": false}')
assert schema.exclusiveMaximum is None
assert schema.maximum == 100
Loading