Skip to content

back to OpenHands summary

OpenHands: fastapi

Failed to run pytests for test tests

Pytest collection failure.

Patch diff

diff --git a/fastapi/_compat.py b/fastapi/_compat.py
index 0e3afa8e..cd89bba3 100644
--- a/fastapi/_compat.py
+++ b/fastapi/_compat.py
@@ -9,8 +9,97 @@ from pydantic import BaseModel, create_model
 from pydantic.version import VERSION as P_VERSION
 from starlette.datastructures import UploadFile
 from typing_extensions import Annotated, Literal, get_args, get_origin
+from pydantic.v1.utils import update_not_none
 PYDANTIC_VERSION = P_VERSION
 PYDANTIC_V2 = PYDANTIC_VERSION.startswith('2.')
+
+def _model_rebuild(cls: Type[BaseModel]) -> None:
+    """Rebuild a pydantic model, updating its configuration.
+    
+    This is a compatibility function for Pydantic v2 that mimics the behavior of
+    the original _model_rebuild function from Pydantic v1.
+    """
+    if not PYDANTIC_V2:
+        from pydantic.main import _model_rebuild as v1_model_rebuild
+        return v1_model_rebuild(cls)
+    
+    # For Pydantic v2, we need to force model rebuild by clearing caches
+    if hasattr(cls, '__pydantic_validator__'):
+        delattr(cls, '__pydantic_validator__')
+    if hasattr(cls, '__pydantic_serializer__'):
+        delattr(cls, '__pydantic_serializer__')
+    if hasattr(cls, '__pydantic_core_schema__'):
+        delattr(cls, '__pydantic_core_schema__')
+    cls.__pydantic_complete__ = False
+    
+    # Use Pydantic v1 model rebuild to handle circular references
+    from pydantic.v1.main import _model_rebuild as v1_model_rebuild
+    
+    # Create a new model with the same configuration
+    from pydantic.v1.main import create_model
+    new_model = create_model(
+        cls.__name__,
+        __base__=cls,
+        __module__=cls.__module__,
+        __validators__=cls.__dict__.get('__validators__', {}),
+        __cls_kwargs__=cls.__dict__.get('__cls_kwargs__', {}),
+    )
+    
+    # Rebuild the model using Pydantic v1
+    v1_model_rebuild(new_model)
+    
+    # Copy the rebuilt model's attributes back to the original class
+    for attr in ('__fields__', '__validators__', '__pre_root_validators__', '__post_root_validators__',
+                '__config__', '__schema_cache__', '__json_encoder__', '__custom_root_type__',
+                '__private_attributes__', '__slots__', '__class_vars__', '__fields_set__'):
+        if hasattr(new_model, attr):
+            setattr(cls, attr, getattr(new_model, attr))
+    
+    # Add model validators to handle circular references
+    from pydantic import model_validator
+    
+    @model_validator(mode='before')
+    def validate_refs(cls, values: Dict[str, Any]) -> Dict[str, Any]:
+        if not isinstance(values, dict):
+            return values
+        for field_name, field_value in values.items():
+            if isinstance(field_value, dict):
+                if '$ref' in field_value:
+                    from fastapi.openapi.models import Reference
+                    values[field_name] = Reference(**field_value)
+                else:
+                    # Check if the field is a reference to another model
+                    from fastapi.openapi.models import Schema, Operation, Encoding, RequestBody, Response, PathItem, Header
+                    if field_name in ('schema', 'schema_', 'items', 'contains', 'additionalProperties', 'propertyNames', 'unevaluatedItems', 'unevaluatedProperties', 'contentSchema'):
+                        values[field_name] = Schema(**field_value)
+                    elif field_name == 'requestBody':
+                        values[field_name] = RequestBody(**field_value)
+                    elif field_name == 'responses':
+                        values[field_name] = {k: Response(**v) for k, v in field_value.items()}
+                    elif field_name == 'callbacks':
+                        values[field_name] = {k: {url: PathItem(**item) for url, item in v.items()} for k, v in field_value.items()}
+                    elif field_name == 'headers':
+                        values[field_name] = {k: Header(**v) for k, v in field_value.items()}
+                    else:
+                        values[field_name] = cls(**field_value)
+            elif isinstance(field_value, list):
+                values[field_name] = [
+                    Reference(**v) if isinstance(v, dict) and '$ref' in v else cls(**v) if isinstance(v, dict) else v
+                    for v in field_value
+                ]
+        return values
+    
+    # Add the validator to the model
+    if not hasattr(cls, 'model_validators'):
+        cls.model_validators = []
+    if validate_refs not in cls.model_validators:
+        cls.model_validators.append(validate_refs)
+    
+    # Force model rebuild
+    from pydantic.v1.main import _model_rebuild as v1_model_rebuild
+    v1_model_rebuild(cls)
+    
+    return cls
 sequence_annotation_to_type = {Sequence: list, List: list, list: list, Tuple: tuple, tuple: tuple, Set: set, set: set, FrozenSet: frozenset, frozenset: frozenset, Deque: deque, deque: deque}
 sequence_types = tuple(sequence_annotation_to_type.keys())
 if PYDANTIC_V2:
@@ -20,6 +109,7 @@ if PYDANTIC_V2:
     from pydantic._internal._schema_generation_shared import GetJsonSchemaHandler as GetJsonSchemaHandler
     from pydantic._internal._typing_extra import eval_type_lenient
     from pydantic._internal._utils import lenient_issubclass as lenient_issubclass
+    from pydantic._internal._model_construction import _model_rebuild
     from pydantic.fields import FieldInfo
     from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema
     from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue
diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py
index 1507fa7f..4bbd8590 100644
--- a/fastapi/openapi/models.py
+++ b/fastapi/openapi/models.py
@@ -1,10 +1,14 @@
 from enum import Enum
 from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Type, Union
-from fastapi._compat import PYDANTIC_V2, CoreSchema, GetJsonSchemaHandler, JsonSchemaValue, _model_rebuild, with_info_plain_validator_function
+from fastapi._compat import PYDANTIC_V2, CoreSchema, GetJsonSchemaHandler, JsonSchemaValue, with_info_plain_validator_function
 from fastapi.logger import logger
-from pydantic import AnyUrl, BaseModel, Field
+from pydantic import AnyUrl, BaseModel, Field, model_validator
 from typing_extensions import Annotated, Literal, TypedDict
 from typing_extensions import deprecated as typing_deprecated
+from pydantic.v1.utils import update_not_none
+from pydantic.v1.main import ModelMetaclass
+from pydantic.v1.main import create_model
+from pydantic.v1.main import _model_rebuild
 try:
     import email_validator
     assert email_validator
@@ -330,6 +334,137 @@ class OpenAPI(BaseModelWithConfig):
     security: Optional[List[Dict[str, List[str]]]] = None
     tags: Optional[List[Tag]] = None
     externalDocs: Optional[ExternalDocumentation] = None
-_model_rebuild(Schema)
-_model_rebuild(Operation)
-_model_rebuild(Encoding)
\ No newline at end of file
+# Handle circular references using model validators
+@model_validator(mode='before')
+def validate_schema_refs(cls, values: Dict[str, Any]) -> Dict[str, Any]:
+    if not isinstance(values, dict):
+        return values
+    if 'allOf' in values:
+        values['allOf'] = [v if isinstance(v, bool) else Schema(**v) for v in values['allOf']]
+    if 'anyOf' in values:
+        values['anyOf'] = [v if isinstance(v, bool) else Schema(**v) for v in values['anyOf']]
+    if 'oneOf' in values:
+        values['oneOf'] = [v if isinstance(v, bool) else Schema(**v) for v in values['oneOf']]
+    if 'not_' in values:
+        values['not_'] = values['not_'] if isinstance(values['not_'], bool) else Schema(**values['not_'])
+    if 'if_' in values:
+        values['if_'] = values['if_'] if isinstance(values['if_'], bool) else Schema(**values['if_'])
+    if 'then' in values:
+        values['then'] = values['then'] if isinstance(values['then'], bool) else Schema(**values['then'])
+    if 'else_' in values:
+        values['else_'] = values['else_'] if isinstance(values['else_'], bool) else Schema(**values['else_'])
+    if 'dependentSchemas' in values:
+        values['dependentSchemas'] = {k: v if isinstance(v, bool) else Schema(**v) for k, v in values['dependentSchemas'].items()}
+    if 'prefixItems' in values:
+        values['prefixItems'] = [v if isinstance(v, bool) else Schema(**v) for v in values['prefixItems']]
+    if 'items' in values:
+        if isinstance(values['items'], list):
+            values['items'] = [v if isinstance(v, bool) else Schema(**v) for v in values['items']]
+        else:
+            values['items'] = values['items'] if isinstance(values['items'], bool) else Schema(**values['items'])
+    if 'contains' in values:
+        values['contains'] = values['contains'] if isinstance(values['contains'], bool) else Schema(**values['contains'])
+    if 'properties' in values:
+        values['properties'] = {k: v if isinstance(v, bool) else Schema(**v) for k, v in values['properties'].items()}
+    if 'patternProperties' in values:
+        values['patternProperties'] = {k: v if isinstance(v, bool) else Schema(**v) for k, v in values['patternProperties'].items()}
+    if 'additionalProperties' in values:
+        values['additionalProperties'] = values['additionalProperties'] if isinstance(values['additionalProperties'], bool) else Schema(**values['additionalProperties'])
+    if 'propertyNames' in values:
+        values['propertyNames'] = values['propertyNames'] if isinstance(values['propertyNames'], bool) else Schema(**values['propertyNames'])
+    if 'unevaluatedItems' in values:
+        values['unevaluatedItems'] = values['unevaluatedItems'] if isinstance(values['unevaluatedItems'], bool) else Schema(**values['unevaluatedItems'])
+    if 'unevaluatedProperties' in values:
+        values['unevaluatedProperties'] = values['unevaluatedProperties'] if isinstance(values['unevaluatedProperties'], bool) else Schema(**values['unevaluatedProperties'])
+    if 'contentSchema' in values:
+        values['contentSchema'] = values['contentSchema'] if isinstance(values['contentSchema'], bool) else Schema(**values['contentSchema'])
+    return values
+
+@model_validator(mode='before')
+def validate_operation_refs(cls, values: Dict[str, Any]) -> Dict[str, Any]:
+    if not isinstance(values, dict):
+        return values
+    if 'requestBody' in values:
+        values['requestBody'] = Reference(**values['requestBody']) if '$ref' in values['requestBody'] else RequestBody(**values['requestBody'])
+    if 'responses' in values:
+        values['responses'] = {k: Reference(**v) if '$ref' in v else Response(**v) for k, v in values['responses'].items()}
+    if 'callbacks' in values:
+        values['callbacks'] = {k: Reference(**v) if '$ref' in v else {url: PathItem(**item) for url, item in v.items()} for k, v in values['callbacks'].items()}
+    return values
+
+@model_validator(mode='before')
+def validate_encoding_refs(cls, values: Dict[str, Any]) -> Dict[str, Any]:
+    if not isinstance(values, dict):
+        return values
+    if 'headers' in values:
+        values['headers'] = {k: Reference(**v) if '$ref' in v else Header(**v) for k, v in values['headers'].items()}
+    return values
+
+# Handle circular references using model validators
+if PYDANTIC_V2:
+    # Create new models with the same configuration
+    Schema = create_model(
+        'Schema',
+        __base__=Schema,
+        __module__=Schema.__module__,
+        __validators__=Schema.__dict__.get('__validators__', {}),
+        __cls_kwargs__=Schema.__dict__.get('__cls_kwargs__', {}),
+    )
+    Operation = create_model(
+        'Operation',
+        __base__=Operation,
+        __module__=Operation.__module__,
+        __validators__=Operation.__dict__.get('__validators__', {}),
+        __cls_kwargs__=Operation.__dict__.get('__cls_kwargs__', {}),
+    )
+    Encoding = create_model(
+        'Encoding',
+        __base__=Encoding,
+        __module__=Encoding.__module__,
+        __validators__=Encoding.__dict__.get('__validators__', {}),
+        __cls_kwargs__=Encoding.__dict__.get('__cls_kwargs__', {}),
+    )
+    
+    # Add model validators to handle circular references
+    @model_validator(mode='before')
+    def validate_refs(cls, values: Dict[str, Any]) -> Dict[str, Any]:
+        if not isinstance(values, dict):
+            return values
+        for field_name, field_value in values.items():
+            if isinstance(field_value, dict):
+                if '$ref' in field_value:
+                    values[field_name] = Reference(**field_value)
+                else:
+                    # Check if the field is a reference to another model
+                    if field_name in ('schema', 'schema_', 'items', 'contains', 'additionalProperties', 'propertyNames', 'unevaluatedItems', 'unevaluatedProperties', 'contentSchema'):
+                        values[field_name] = Schema(**field_value)
+                    elif field_name == 'requestBody':
+                        values[field_name] = RequestBody(**field_value)
+                    elif field_name == 'responses':
+                        values[field_name] = {k: Response(**v) for k, v in field_value.items()}
+                    elif field_name == 'callbacks':
+                        values[field_name] = {k: {url: PathItem(**item) for url, item in v.items()} for k, v in field_value.items()}
+                    elif field_name == 'headers':
+                        values[field_name] = {k: Header(**v) for k, v in field_value.items()}
+                    else:
+                        values[field_name] = cls(**field_value)
+            elif isinstance(field_value, list):
+                values[field_name] = [
+                    Reference(**v) if isinstance(v, dict) and '$ref' in v else cls(**v) if isinstance(v, dict) else v
+                    for v in field_value
+                ]
+        return values
+    
+    # Add the validator to the models
+    for model in (Schema, Operation, Encoding):
+        if not hasattr(model, 'model_validators'):
+            model.model_validators = []
+        if validate_refs not in model.model_validators:
+            model.model_validators.append(validate_refs)
+        # Force model rebuild
+        _model_rebuild(model)
+else:
+    # Use Pydantic v1 model rebuild to handle circular references
+    _model_rebuild(Schema)
+    _model_rebuild(Operation)
+    _model_rebuild(Encoding)
\ No newline at end of file