Skip to content

Commit

Permalink
Fix pydantic name annotated is not defined main (#364)
Browse files Browse the repository at this point in the history
* Implement patch for same schema in parameters issues

* Add schema test scenario

* Rebuild docs

* Fix template codegen test
  • Loading branch information
sternakt authored Oct 12, 2024
1 parent 727e727 commit dca0add
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 24 deletions.
2 changes: 2 additions & 0 deletions docs/docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ search:
- fastapi_code_generator_helpers
- [ArgumentWithDescription](api/fastagency/api/openapi/fastapi_code_generator_helpers/ArgumentWithDescription.md)
- [patch_get_parameter_type](api/fastagency/api/openapi/fastapi_code_generator_helpers/patch_get_parameter_type.md)
- patch_fastapi_code_generator
- [patch_parse_schema](api/fastagency/api/openapi/patch_fastapi_code_generator/patch_parse_schema.md)
- security
- [APIKeyCookie](api/fastagency/api/openapi/security/APIKeyCookie.md)
- [APIKeyHeader](api/fastagency/api/openapi/security/APIKeyHeader.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
# 0.5 - API
# 2 - Release
# 3 - Contributing
# 5 - Template Page
# 10 - Default
search:
boost: 0.5
---

::: fastagency.api.openapi.patch_fastapi_code_generator.patch_parse_schema
3 changes: 3 additions & 0 deletions fastagency/api/openapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
check_imports(["fastapi_code_generator", "fastapi", "requests"], "openapi")

from .client import OpenAPI # noqa: E402
from .patch_fastapi_code_generator import patch_parse_schema # noqa: E402

patch_parse_schema()

__all__ = ["OpenAPI"]
22 changes: 22 additions & 0 deletions fastagency/api/openapi/patch_fastapi_code_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from functools import wraps
from typing import Any

from fastapi_code_generator.parser import OpenAPIParser

from ...logging import get_logger

logger = get_logger(__name__)


def patch_parse_schema() -> None:
org_parse_schema = OpenAPIParser.parse_schema

@wraps(org_parse_schema)
def my_parse_schema(*args: Any, **kwargs: Any) -> Any:
data_type = org_parse_schema(*args, **kwargs)
if data_type.reference and data_type.reference.duplicate_name:
data_type.reference.name = data_type.reference.duplicate_name
return data_type

OpenAPIParser.parse_schema = my_parse_schema
logger.info("Patched OpenAPIParser.parse_schema")
2 changes: 1 addition & 1 deletion tests/api/openapi/templates/openapi.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"title": "openapi",
"version": "0.1.0"
},
"servers": [
Expand Down
2 changes: 1 addition & 1 deletion tests/api/openapi/templates/openapi2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"title": "openapi2",
"license": {
"name": "MIT"
}
Expand Down
88 changes: 88 additions & 0 deletions tests/api/openapi/templates/same_schema_in_parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"openapi": "3.1.0",
"info": {
"title": "same_schema_in_parameters",
"version": "0.1.0"
},
"servers": [
{
"url": "http://127.0.0.1:50369",
"description": "Local development server"
}
],
"paths": {
"/get-sheet": {
"get": {
"summary": "Get Sheet",
"description": "Get data from a Google Sheet",
"operationId": "get_sheet_get_sheet_get",
"parameters": [
{
"name": "spreadsheet_id",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
}
],
"title": "Spreadsheet Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "string",
"title": "Response Get Sheet Get Sheet Get"
}
}
}
}
}
}
},
"/update-sheet": {
"post": {
"summary": "Update Sheet",
"description": "Update data in a Google Sheet within the existing spreadsheet",
"operationId": "update_sheet_update_sheet_post",
"parameters": [
{
"name": "spreadsheet_id",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
}
],
"title": "Spreadsheet Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "string",
"title": "Response Update Sheet Update Sheet Post"
}
}
}
}
}
}
},
"components": {
"schemas": {}
}
}
}
41 changes: 19 additions & 22 deletions tests/api/openapi/templates/test_fastapi_codegen_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,29 @@
import sys
import tempfile
from pathlib import Path
from typing import Any, Union

from _pytest.monkeypatch import MonkeyPatch
import pytest
from datamodel_code_generator import DataModelType
from fastapi_code_generator.__main__ import generate_code

OPENAPI_FILE_PATH = (Path(__file__).parent / "openapi.json").resolve()
OPENAPI_FILE_PATHS = Path(__file__).parent.glob("*.json")
TEMPLATE_DIR = Path(__file__).parents[4] / "templates"

assert OPENAPI_FILE_PATH.exists(), OPENAPI_FILE_PATH
assert TEMPLATE_DIR.exists(), TEMPLATE_DIR


class MockResponse:
def __init__(
self, json_data: Union[list[dict[str, Any]], dict[str, Any]], status_code: int
) -> None:
"""Mock response object for requests."""
self.json_data = json_data
self.status_code = status_code

def json(self) -> Union[list[dict[str, Any]], dict[str, Any]]:
"""Return the json data."""
return self.json_data


def test_fastapi_codegen_template(monkeypatch: MonkeyPatch) -> None:
@pytest.mark.parametrize("openapi_file_path", OPENAPI_FILE_PATHS)
def test_fastapi_codegen_template(openapi_file_path: Path) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
td = Path(temp_dir)

generate_code(
input_name=OPENAPI_FILE_PATH.name,
input_text=OPENAPI_FILE_PATH.read_text(),
input_name=openapi_file_path.name,
input_text=openapi_file_path.read_text(),
encoding="utf-8",
output_dir=td,
template_dir=TEMPLATE_DIR,
output_model_type=DataModelType.PydanticV2BaseModel,
)

main_path = td / "main.py"
Expand All @@ -46,12 +34,21 @@ def test_fastapi_codegen_template(monkeypatch: MonkeyPatch) -> None:
with open(main_path, "w") as f: # noqa: PTH123
f.write(main_py_code)

original_sys_path = sys.path.copy()
# add td to sys.path
try:
sys.path.append(str(td))
importlib.invalidate_caches()

if "main" in sys.modules:
del sys.modules["main"]
if "models" in sys.modules:
del sys.modules["models"]

main = importlib.import_module("main", package=td.name) # nosemgrep
importlib.reload(main)
finally:
sys.path.remove(str(td))
sys.path = original_sys_path

app = main.app
assert app.title == "FastAPI"
assert app.title == openapi_file_path.stem
113 changes: 113 additions & 0 deletions tests/api/openapi/test_openapi_same_schema_in_parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from typing import Annotated, Any, Optional

import pytest
from autogen.agentchat import ConversableAgent
from fastapi import FastAPI


def create_app_with_two_enpoints_same_datamodel(host: str, port: str) -> FastAPI:
app = FastAPI(
title="test",
servers=[
{"url": f"http://{host}:{port}", "description": "Local development server"}
],
)

@app.get("/get-sheet", description="Get data from a Google Sheet")
async def get_sheet(
spreadsheet_id: Annotated[
Optional[str], "ID of the Google Sheet to fetch data from"
] = None,
) -> str:
return "sheet retrieved"

@app.post(
"/update-sheet",
description="Update data in a Google Sheet within the existing spreadsheet",
)
async def update_sheet(
spreadsheet_id: Annotated[
Optional[str], "ID of the Google Sheet to fetch data from"
] = None,
) -> str:
return "Updated"

return app


@pytest.mark.parametrize(
"fastapi_openapi_url",
[(create_app_with_two_enpoints_same_datamodel)],
indirect=["fastapi_openapi_url"],
)
def test_openapi_same_schema_in_parameters_register_for_llm(
fastapi_openapi_url: str,
azure_gpt35_turbo_16k_llm_config: dict[str, Any],
) -> None:
from fastagency.api.openapi import OpenAPI

api_client = OpenAPI.create(
openapi_url=fastapi_openapi_url,
)
agent = ConversableAgent(name="agent", llm_config=azure_gpt35_turbo_16k_llm_config)
api_client._register_for_llm(agent)
tools = agent.llm_config["tools"]

expected = [
{
"type": "function",
"function": {
"description": "Get data from a Google Sheet",
"name": "get_sheet_get_sheet_get",
"parameters": {
"type": "object",
"properties": {
"spreadsheet_id": {
"$defs": {
"SpreadsheetId": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "SpreadsheetId",
}
},
"anyOf": [
{"$ref": "#/$defs/SpreadsheetId"},
{"type": "null"},
],
"default": None,
"description": "spreadsheet_id",
}
},
"required": [],
},
},
},
{
"type": "function",
"function": {
"description": "Update data in a Google Sheet within the existing spreadsheet",
"name": "update_sheet_update_sheet_post",
"parameters": {
"type": "object",
"properties": {
"spreadsheet_id": {
"$defs": {
"SpreadsheetId": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "SpreadsheetId",
}
},
"anyOf": [
{"$ref": "#/$defs/SpreadsheetId"},
{"type": "null"},
],
"default": None,
"description": "spreadsheet_id",
}
},
"required": [],
},
},
},
]

assert tools == expected, tools

0 comments on commit dca0add

Please sign in to comment.