pythonasyncioapiasyncfastapiframeworkjsonjson-schemaopenapiopenapi3pydanticpython-typespython3redocreststarletteswaggerswagger-uiuvicornweb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
6.2 KiB
188 lines
6.2 KiB
"""
|
|
Tests for PropertyNames constraint support in FastAPI.
|
|
"""
|
|
|
|
from typing import Dict
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.schema import PropertyNames
|
|
from pydantic import BaseModel
|
|
from typing_extensions import Annotated
|
|
|
|
|
|
def test_property_names_creation():
|
|
"""Test PropertyNames object creation and validation."""
|
|
# Test pattern constraint
|
|
pn1 = PropertyNames(pattern="^test_")
|
|
assert pn1.get_constraint_schema() == {"pattern": "^test_"}
|
|
|
|
# Test multiple constraints
|
|
pn2 = PropertyNames(min_length=5, max_length=20, pattern="^[a-z]+$")
|
|
expected = {"minLength": 5, "maxLength": 20, "pattern": "^[a-z]+$"}
|
|
assert pn2.get_constraint_schema() == expected
|
|
|
|
# Test schema constraint
|
|
pn3 = PropertyNames(schema={"type": "string", "format": "email"})
|
|
expected = {"type": "string", "format": "email"}
|
|
assert pn3.get_constraint_schema() == expected
|
|
|
|
# Test empty constraint should raise error
|
|
try:
|
|
PropertyNames()
|
|
assert False, "Should have raised ValueError"
|
|
except ValueError as e:
|
|
assert (
|
|
"PropertyNames constraint must specify at least one validation rule"
|
|
in str(e)
|
|
)
|
|
|
|
|
|
def test_property_names_pattern_constraint():
|
|
"""Test PropertyNames with pattern constraint in FastAPI schema generation."""
|
|
|
|
class ModelWithPropertyNames(BaseModel):
|
|
properties: Annotated[Dict[str, str], PropertyNames(pattern="^prefix_")]
|
|
|
|
app = FastAPI()
|
|
|
|
@app.post("/test")
|
|
def test_endpoint(data: ModelWithPropertyNames):
|
|
return data
|
|
|
|
# Get the OpenAPI schema
|
|
openapi_schema = app.openapi()
|
|
|
|
# Find the schema for our model
|
|
model_schema = None
|
|
for schema_name, schema_def in openapi_schema["components"]["schemas"].items():
|
|
if schema_name == "ModelWithPropertyNames":
|
|
properties_field = schema_def["properties"]["properties"]
|
|
if "propertyNames" in properties_field:
|
|
model_schema = properties_field
|
|
break
|
|
|
|
assert model_schema is not None, "Could not find model schema with propertyNames"
|
|
|
|
# Verify the propertyNames constraint is present
|
|
property_names_constraint = model_schema["propertyNames"]
|
|
assert property_names_constraint["pattern"] == "^prefix_"
|
|
|
|
# Verify the full schema structure
|
|
expected_keys = {"type", "additionalProperties", "propertyNames", "title"}
|
|
assert set(model_schema.keys()) >= expected_keys
|
|
|
|
|
|
def test_property_names_complex_constraints():
|
|
"""Test PropertyNames with multiple constraints."""
|
|
|
|
class ComplexPropertyNames(BaseModel):
|
|
config_vars: Annotated[
|
|
Dict[str, str],
|
|
PropertyNames(min_length=3, max_length=50, pattern="^[A-Z_]+$"),
|
|
]
|
|
|
|
app = FastAPI()
|
|
|
|
@app.post("/test")
|
|
def test_endpoint(data: ComplexPropertyNames):
|
|
return data
|
|
|
|
# Get the OpenAPI schema
|
|
openapi_schema = app.openapi()
|
|
|
|
# Find the schema for our model
|
|
model_schema = None
|
|
for schema_name, schema_def in openapi_schema["components"]["schemas"].items():
|
|
if schema_name == "ComplexPropertyNames":
|
|
config_vars_field = schema_def["properties"]["config_vars"]
|
|
if "propertyNames" in config_vars_field:
|
|
model_schema = config_vars_field
|
|
break
|
|
|
|
assert model_schema is not None, "Could not find model schema with propertyNames"
|
|
|
|
# Verify the propertyNames constraint has all expected properties
|
|
property_names_constraint = model_schema["propertyNames"]
|
|
assert property_names_constraint["pattern"] == "^[A-Z_]+$"
|
|
assert property_names_constraint["minLength"] == 3
|
|
assert property_names_constraint["maxLength"] == 50
|
|
|
|
|
|
def test_property_names_schema_constraint():
|
|
"""Test PropertyNames with custom schema constraint."""
|
|
|
|
class SchemaPropertyNames(BaseModel):
|
|
allowed_keys: Annotated[
|
|
Dict[str, int],
|
|
PropertyNames(schema={"type": "string", "enum": ["key1", "key2", "key3"]}),
|
|
]
|
|
|
|
app = FastAPI()
|
|
|
|
@app.post("/test")
|
|
def test_endpoint(data: SchemaPropertyNames):
|
|
return data
|
|
|
|
# Get the OpenAPI schema
|
|
openapi_schema = app.openapi()
|
|
|
|
# Find the schema for our model
|
|
model_schema = None
|
|
for schema_name, schema_def in openapi_schema["components"]["schemas"].items():
|
|
if schema_name == "SchemaPropertyNames":
|
|
allowed_keys_field = schema_def["properties"]["allowed_keys"]
|
|
if "propertyNames" in allowed_keys_field:
|
|
model_schema = allowed_keys_field
|
|
break
|
|
|
|
assert model_schema is not None, "Could not find model schema with propertyNames"
|
|
|
|
# Verify the propertyNames constraint with custom schema
|
|
property_names_constraint = model_schema["propertyNames"]
|
|
assert property_names_constraint["type"] == "string"
|
|
assert property_names_constraint["enum"] == ["key1", "key2", "key3"]
|
|
|
|
|
|
def test_property_names_no_constraint():
|
|
"""Test that models without PropertyNames constraints work normally."""
|
|
|
|
class NormalModel(BaseModel):
|
|
properties: Dict[str, str]
|
|
|
|
app = FastAPI()
|
|
|
|
@app.post("/test")
|
|
def test_endpoint(data: NormalModel):
|
|
return data
|
|
|
|
# Get the OpenAPI schema
|
|
openapi_schema = app.openapi()
|
|
|
|
# Find the schema for our model
|
|
for schema_name, schema_def in openapi_schema["components"]["schemas"].items():
|
|
if schema_name == "NormalModel":
|
|
properties_field = schema_def["properties"]["properties"]
|
|
# Should not have propertyNames constraint
|
|
assert "propertyNames" not in properties_field
|
|
break
|
|
else:
|
|
assert False, "Could not find NormalModel schema"
|
|
|
|
|
|
def test_property_names_kwargs():
|
|
"""Test PropertyNames with additional kwargs."""
|
|
pn = PropertyNames(format="email", description="Email addresses")
|
|
constraint_schema = pn.get_constraint_schema()
|
|
|
|
assert constraint_schema["format"] == "email"
|
|
assert constraint_schema["description"] == "Email addresses"
|
|
|
|
|
|
def test_property_names_repr():
|
|
"""Test PropertyNames string representation."""
|
|
pn = PropertyNames(pattern="^test_")
|
|
repr_str = repr(pn)
|
|
|
|
assert "PropertyNames" in repr_str
|
|
assert "pattern" in repr_str
|
|
assert "^test_" in repr_str
|
|
|