Jeremiah Lowin commited on
Commit
4963138
·
unverified ·
2 Parent(s): 79b3dfe0b1b3ab

Merge pull request #113 from jlowin/uv-sync

Browse files
README.md CHANGED
@@ -113,7 +113,7 @@ For development, install with:
113
  git clone https://github.com/jlowin/fastmcp.git
114
  cd fastmcp
115
  # Install with dev dependencies
116
- uv sync --dev
117
  ```
118
 
119
  ## Quickstart
@@ -589,7 +589,7 @@ Contributions make the open-source community vibrant! We welcome improvements an
589
  #### Setup
590
 
591
  1. Clone: `git clone https://github.com/jlowin/fastmcp.git && cd fastmcp`
592
- 2. Install Env & Dependencies: `uv venv && uv sync --dev` (Activate the `.venv` after creation)
593
 
594
  #### Testing
595
 
 
113
  git clone https://github.com/jlowin/fastmcp.git
114
  cd fastmcp
115
  # Install with dev dependencies
116
+ uv sync
117
  ```
118
 
119
  ## Quickstart
 
589
  #### Setup
590
 
591
  1. Clone: `git clone https://github.com/jlowin/fastmcp.git && cd fastmcp`
592
+ 2. Install Env & Dependencies: `uv venv && uv sync` (Activate the `.venv` after creation)
593
 
594
  #### Testing
595
 
examples/mount_example.py CHANGED
@@ -9,7 +9,6 @@ the ToolManager's import_tools functionality. It shows how to:
9
  """
10
 
11
  import asyncio
12
- from typing import Dict, List
13
 
14
  from fastmcp import FastMCP
15
 
@@ -34,7 +33,7 @@ news_app = FastMCP("News App")
34
 
35
 
36
  @news_app.tool()
37
- def get_news_headlines() -> List[str]:
38
  """Get the latest news headlines."""
39
  return [
40
  "Tech company launches new product",
@@ -58,7 +57,7 @@ app = FastMCP("Main App")
58
 
59
 
60
  @app.tool()
61
- def check_app_status() -> Dict[str, str]:
62
  """Check the status of the main application."""
63
  return {"status": "running", "version": "1.0.0", "uptime": "3h 24m"}
64
 
 
9
  """
10
 
11
  import asyncio
 
12
 
13
  from fastmcp import FastMCP
14
 
 
33
 
34
 
35
  @news_app.tool()
36
+ def get_news_headlines() -> list[str]:
37
  """Get the latest news headlines."""
38
  return [
39
  "Tech company launches new product",
 
57
 
58
 
59
  @app.tool()
60
+ def check_app_status() -> dict[str, str]:
61
  """Check the status of the main application."""
62
  return {"status": "running", "version": "1.0.0", "uptime": "3h 24m"}
63
 
pyproject.toml CHANGED
@@ -16,12 +16,6 @@ requires-python = ">=3.10"
16
  readme = "README.md"
17
  license = { text = "Apache-2.0" }
18
 
19
- [project.scripts]
20
- fastmcp = "fastmcp.cli:app"
21
-
22
- [project.optional-dependencies]
23
-
24
-
25
  [dependency-groups]
26
  dev = [
27
  "pre-commit",
@@ -36,23 +30,32 @@ dev = [
36
  "pdbpp>=0.10.3",
37
  "dirty-equals>=0.9.0",
38
  ]
 
 
 
 
39
  [build-system]
40
- requires = ["hatchling>=1.21.0", "hatch-vcs>=0.4.0"]
41
  build-backend = "hatchling.build"
42
 
 
 
 
 
 
 
 
 
 
43
  [tool.uv]
44
- # no default groups
45
- default-groups = []
46
 
47
  [tool.pytest.ini_options]
48
  asyncio_mode = "auto"
49
  asyncio_default_fixture_loop_scope = "session"
50
- filterwarnings = [
51
- "ignore:Accessing the 'model_fields' attribute on the instance is deprecated:DeprecationWarning",
52
- ]
53
 
54
- [tool.hatch.version]
55
- source = "vcs"
56
 
57
  [tool.pyright]
58
  include = ["src", "tests"]
@@ -67,7 +70,7 @@ venvPath = "."
67
  venv = ".venv"
68
 
69
  [tool.ruff.lint]
70
- extend-select = ["I"]
71
 
72
  [tool.ruff.lint.per-file-ignores]
73
  "__init__.py" = ["F401", "I001", "RUF013"]
 
16
  readme = "README.md"
17
  license = { text = "Apache-2.0" }
18
 
 
 
 
 
 
 
19
  [dependency-groups]
20
  dev = [
21
  "pre-commit",
 
30
  "pdbpp>=0.10.3",
31
  "dirty-equals>=0.9.0",
32
  ]
33
+
34
+ [project.scripts]
35
+ fastmcp = "fastmcp.cli:app"
36
+
37
  [build-system]
38
+ requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
39
  build-backend = "hatchling.build"
40
 
41
+ [tool.hatch.version]
42
+ source = "uv-dynamic-versioning"
43
+
44
+ [tool.uv-dynamic-versioning]
45
+ vcs = "git"
46
+ style = "pep440"
47
+ bump = true
48
+ fallback-version = "0.0.0"
49
+
50
  [tool.uv]
51
+ # uncomment to omit `dev` default group
52
+ # default-groups = []
53
 
54
  [tool.pytest.ini_options]
55
  asyncio_mode = "auto"
56
  asyncio_default_fixture_loop_scope = "session"
57
+ filterwarnings = []
 
 
58
 
 
 
59
 
60
  [tool.pyright]
61
  include = ["src", "tests"]
 
70
  venv = ".venv"
71
 
72
  [tool.ruff.lint]
73
+ extend-select = ["I", "UP"]
74
 
75
  [tool.ruff.lint.per-file-ignores]
76
  "__init__.py" = ["F401", "I001", "RUF013"]
src/fastmcp/client/client.py CHANGED
@@ -1,6 +1,7 @@
1
  import datetime
 
2
  from pathlib import Path
3
- from typing import Any, AsyncContextManager
4
 
5
  import mcp.types
6
  from mcp import ClientSession
@@ -48,7 +49,7 @@ class Client:
48
  ):
49
  self.transport = infer_transport(transport)
50
  self._session: ClientSession | None = None
51
- self._session_cm: AsyncContextManager[ClientSession] | None = None
52
 
53
  # Store common kwargs to pass to transport.connect_session
54
  if roots is not None and list_roots_callback is not None:
 
1
  import datetime
2
+ from contextlib import AbstractAsyncContextManager
3
  from pathlib import Path
4
+ from typing import Any
5
 
6
  import mcp.types
7
  from mcp import ClientSession
 
49
  ):
50
  self.transport = infer_transport(transport)
51
  self._session: ClientSession | None = None
52
+ self._session_cm: AbstractAsyncContextManager[ClientSession] | None = None
53
 
54
  # Store common kwargs to pass to transport.connect_session
55
  if roots is not None and list_roots_callback is not None:
src/fastmcp/client/transports.py CHANGED
@@ -2,14 +2,10 @@ import abc
2
  import contextlib
3
  import datetime
4
  import os
 
5
  from pathlib import Path
6
  from typing import (
7
- AsyncIterator,
8
- Dict,
9
- List,
10
- Optional,
11
  TypedDict,
12
- Union,
13
  )
14
 
15
  from mcp import ClientSession, StdioServerParameters
@@ -103,7 +99,7 @@ class WSTransport(ClientTransport):
103
  class SSETransport(ClientTransport):
104
  """Transport implementation that connects to an MCP server via Server-Sent Events."""
105
 
106
- def __init__(self, url: str | AnyUrl, headers: Optional[Dict[str, str]] = None):
107
  if isinstance(url, AnyUrl):
108
  url = str(url)
109
  if not isinstance(url, str) or not url.startswith("http"):
@@ -138,9 +134,9 @@ class StdioTransport(ClientTransport):
138
  def __init__(
139
  self,
140
  command: str,
141
- args: List[str],
142
- env: Optional[Dict[str, str]] = None,
143
- cwd: Optional[str] = None,
144
  ):
145
  """
146
  Initialize a Stdio transport.
@@ -182,10 +178,10 @@ class PythonStdioTransport(StdioTransport):
182
 
183
  def __init__(
184
  self,
185
- script_path: Union[str, Path],
186
- args: Optional[List[str]] = None,
187
- env: Optional[Dict[str, str]] = None,
188
- cwd: Optional[str] = None,
189
  python_cmd: str = "python",
190
  ):
191
  """
@@ -217,10 +213,10 @@ class NodeStdioTransport(StdioTransport):
217
 
218
  def __init__(
219
  self,
220
- script_path: Union[str, Path],
221
- args: Optional[List[str]] = None,
222
- env: Optional[Dict[str, str]] = None,
223
- cwd: Optional[str] = None,
224
  node_cmd: str = "node",
225
  ):
226
  """
@@ -253,12 +249,12 @@ class UvxStdioTransport(StdioTransport):
253
  def __init__(
254
  self,
255
  tool_name: str,
256
- tool_args: Optional[List[str]] = None,
257
- project_directory: Optional[str] = None,
258
- python_version: Optional[str] = None,
259
- with_packages: Optional[List[str]] = None,
260
- from_package: Optional[str] = None,
261
- env_vars: Optional[Dict[str, str]] = None,
262
  ):
263
  """
264
  Initialize a Uvx transport.
@@ -308,9 +304,9 @@ class NpxStdioTransport(StdioTransport):
308
  def __init__(
309
  self,
310
  package: str,
311
- args: Optional[List[str]] = None,
312
- project_directory: Optional[str] = None,
313
- env_vars: Optional[Dict[str, str]] = None,
314
  use_package_lock: bool = True,
315
  ):
316
  """
@@ -394,7 +390,7 @@ def infer_transport(
394
  return FastMCPTransport(mcp=transport)
395
 
396
  # the transport is a path to a script
397
- elif isinstance(transport, (Path, str)) and Path(transport).exists():
398
  if str(transport).endswith(".py"):
399
  return PythonStdioTransport(script_path=transport)
400
  elif str(transport).endswith(".js"):
@@ -403,11 +399,11 @@ def infer_transport(
403
  raise ValueError(f"Unsupported script type: {transport}")
404
 
405
  # the transport is an http(s) URL
406
- elif isinstance(transport, (AnyUrl, str)) and str(transport).startswith("http"):
407
  return SSETransport(url=transport)
408
 
409
  # the transport is a websocket URL
410
- elif isinstance(transport, (AnyUrl, str)) and str(transport).startswith("ws"):
411
  return WSTransport(url=transport)
412
 
413
  # the transport is an unknown type
 
2
  import contextlib
3
  import datetime
4
  import os
5
+ from collections.abc import AsyncIterator
6
  from pathlib import Path
7
  from typing import (
 
 
 
 
8
  TypedDict,
 
9
  )
10
 
11
  from mcp import ClientSession, StdioServerParameters
 
99
  class SSETransport(ClientTransport):
100
  """Transport implementation that connects to an MCP server via Server-Sent Events."""
101
 
102
+ def __init__(self, url: str | AnyUrl, headers: dict[str, str] | None = None):
103
  if isinstance(url, AnyUrl):
104
  url = str(url)
105
  if not isinstance(url, str) or not url.startswith("http"):
 
134
  def __init__(
135
  self,
136
  command: str,
137
+ args: list[str],
138
+ env: dict[str, str] | None = None,
139
+ cwd: str | None = None,
140
  ):
141
  """
142
  Initialize a Stdio transport.
 
178
 
179
  def __init__(
180
  self,
181
+ script_path: str | Path,
182
+ args: list[str] | None = None,
183
+ env: dict[str, str] | None = None,
184
+ cwd: str | None = None,
185
  python_cmd: str = "python",
186
  ):
187
  """
 
213
 
214
  def __init__(
215
  self,
216
+ script_path: str | Path,
217
+ args: list[str] | None = None,
218
+ env: dict[str, str] | None = None,
219
+ cwd: str | None = None,
220
  node_cmd: str = "node",
221
  ):
222
  """
 
249
  def __init__(
250
  self,
251
  tool_name: str,
252
+ tool_args: list[str] | None = None,
253
+ project_directory: str | None = None,
254
+ python_version: str | None = None,
255
+ with_packages: list[str] | None = None,
256
+ from_package: str | None = None,
257
+ env_vars: dict[str, str] | None = None,
258
  ):
259
  """
260
  Initialize a Uvx transport.
 
304
  def __init__(
305
  self,
306
  package: str,
307
+ args: list[str] | None = None,
308
+ project_directory: str | None = None,
309
+ env_vars: dict[str, str] | None = None,
310
  use_package_lock: bool = True,
311
  ):
312
  """
 
390
  return FastMCPTransport(mcp=transport)
391
 
392
  # the transport is a path to a script
393
+ elif isinstance(transport, Path | str) and Path(transport).exists():
394
  if str(transport).endswith(".py"):
395
  return PythonStdioTransport(script_path=transport)
396
  elif str(transport).endswith(".js"):
 
399
  raise ValueError(f"Unsupported script type: {transport}")
400
 
401
  # the transport is an http(s) URL
402
+ elif isinstance(transport, AnyUrl | str) and str(transport).startswith("http"):
403
  return SSETransport(url=transport)
404
 
405
  # the transport is a websocket URL
406
+ elif isinstance(transport, AnyUrl | str) and str(transport).startswith("ws"):
407
  return WSTransport(url=transport)
408
 
409
  # the transport is an unknown type
src/fastmcp/server/openapi.py CHANGED
@@ -4,7 +4,8 @@ import enum
4
  import json
5
  import re
6
  from dataclasses import dataclass
7
- from typing import Any, Literal, Pattern
 
8
 
9
  import httpx
10
  from pydantic.networks import AnyUrl
 
4
  import json
5
  import re
6
  from dataclasses import dataclass
7
+ from re import Pattern
8
+ from typing import Any, Literal
9
 
10
  import httpx
11
  from pydantic.networks import AnyUrl
src/fastmcp/server/server.py CHANGED
@@ -99,7 +99,7 @@ class FastMCP(Generic[LifespanResultT]):
99
  self.dependencies = self.settings.dependencies
100
 
101
  # Setup for mounted apps
102
- self._mounted_apps: dict[str, "FastMCP"] = {}
103
 
104
  # Set up MCP protocol handlers
105
  self._setup_handlers()
@@ -640,7 +640,7 @@ def _convert_to_content(
640
  other_content = []
641
 
642
  for item in result:
643
- if isinstance(item, (TextContent, ImageContent, EmbeddedResource, Image)):
644
  mcp_types.append(_convert_to_content(item)[0])
645
  else:
646
  other_content.append(item)
 
99
  self.dependencies = self.settings.dependencies
100
 
101
  # Setup for mounted apps
102
+ self._mounted_apps: dict[str, FastMCP] = {}
103
 
104
  # Set up MCP protocol handlers
105
  self._setup_handlers()
 
640
  other_content = []
641
 
642
  for item in result:
643
+ if isinstance(item, TextContent | ImageContent | EmbeddedResource | Image):
644
  mcp_types.append(_convert_to_content(item)[0])
645
  else:
646
  other_content.append(item)
src/fastmcp/tools/tool_manager.py CHANGED
@@ -62,7 +62,7 @@ class ToolManager:
62
  return await tool.run(arguments, context=context)
63
 
64
  def import_tools(
65
- self, tool_manager: "ToolManager", prefix: str | None = None
66
  ) -> None:
67
  """
68
  Import all tools from another ToolManager with prefixed names.
 
62
  return await tool.run(arguments, context=context)
63
 
64
  def import_tools(
65
+ self, tool_manager: ToolManager, prefix: str | None = None
66
  ) -> None:
67
  """
68
  Import all tools from another ToolManager with prefixed names.
src/fastmcp/utilities/func_metadata.py CHANGED
@@ -27,7 +27,7 @@ class ArgModelBase(BaseModel):
27
  That is, sub-models etc are not dumped - they are kept as pydantic models.
28
  """
29
  kwargs: dict[str, Any] = {}
30
- for field_name in self.model_fields.keys():
31
  kwargs[field_name] = getattr(self, field_name)
32
  return kwargs
33
 
 
27
  That is, sub-models etc are not dumped - they are kept as pydantic models.
28
  """
29
  kwargs: dict[str, Any] = {}
30
+ for field_name in self.__class__.model_fields.keys():
31
  kwargs[field_name] = getattr(self, field_name)
32
  return kwargs
33
 
src/fastmcp/utilities/openapi.py CHANGED
@@ -1,6 +1,6 @@
1
  import json
2
  import logging
3
- from typing import Any, Literal, Union, cast
4
 
5
  # Using the recommended library: openapi-pydantic
6
  from openapi_pydantic import (
@@ -92,7 +92,7 @@ __all__ = [
92
 
93
 
94
  def _resolve_ref(
95
- item: Union[Reference, Schema, Parameter, RequestBody, Any], openapi: OpenAPI
96
  ) -> Any:
97
  """Resolves a potential Reference object to its target definition (no changes needed here)."""
98
  if isinstance(item, Reference):
@@ -110,7 +110,7 @@ def _resolve_ref(
110
  elif isinstance(target, BaseModel):
111
  # Use model_extra for fields not explicitly defined (like components types)
112
  # Check class fields first, then model_extra
113
- if part in target.model_fields: # Access class attribute here
114
  target = getattr(target, part, None)
115
  elif target.model_extra and part in target.model_extra:
116
  target = target.model_extra[part]
@@ -141,7 +141,7 @@ def _resolve_ref(
141
 
142
 
143
  def _extract_schema_as_dict(
144
- schema_obj: Union[Schema, Reference], openapi: OpenAPI
145
  ) -> JsonSchema:
146
  """Resolves a schema/reference and returns it as a dictionary."""
147
  resolved_schema = _resolve_ref(schema_obj, openapi)
@@ -177,8 +177,8 @@ def _convert_to_parameter_location(param_in: str) -> ParameterLocation:
177
 
178
 
179
  def _extract_parameters(
180
- operation_params: list[Union[Parameter, Reference]] | None,
181
- path_item_params: list[Union[Parameter, Reference]] | None,
182
  openapi: OpenAPI,
183
  ) -> list[ParameterInfo]:
184
  """Extracts and resolves parameters using corrected attribute names."""
 
1
  import json
2
  import logging
3
+ from typing import Any, Literal, cast
4
 
5
  # Using the recommended library: openapi-pydantic
6
  from openapi_pydantic import (
 
92
 
93
 
94
  def _resolve_ref(
95
+ item: Reference | Schema | Parameter | RequestBody | Any, openapi: OpenAPI
96
  ) -> Any:
97
  """Resolves a potential Reference object to its target definition (no changes needed here)."""
98
  if isinstance(item, Reference):
 
110
  elif isinstance(target, BaseModel):
111
  # Use model_extra for fields not explicitly defined (like components types)
112
  # Check class fields first, then model_extra
113
+ if part in target.__class__.model_fields:
114
  target = getattr(target, part, None)
115
  elif target.model_extra and part in target.model_extra:
116
  target = target.model_extra[part]
 
141
 
142
 
143
  def _extract_schema_as_dict(
144
+ schema_obj: Schema | Reference, openapi: OpenAPI
145
  ) -> JsonSchema:
146
  """Resolves a schema/reference and returns it as a dictionary."""
147
  resolved_schema = _resolve_ref(schema_obj, openapi)
 
177
 
178
 
179
  def _extract_parameters(
180
+ operation_params: list[Parameter | Reference] | None,
181
+ path_item_params: list[Parameter | Reference] | None,
182
  openapi: OpenAPI,
183
  ) -> list[ParameterInfo]:
184
  """Extracts and resolves parameters using corrected attribute names."""
tests/utilities/openapi/test_openapi.py CHANGED
@@ -1,6 +1,6 @@
1
  """Tests for the OpenAPI parsing utilities."""
2
 
3
- from typing import Any, Dict
4
 
5
  import pytest
6
  from fastapi import Body, FastAPI, Path, Query
@@ -12,7 +12,7 @@ from fastmcp.utilities.openapi import parse_openapi_to_http_routes
12
 
13
 
14
  @pytest.fixture
15
- def petstore_schema() -> Dict[str, Any]:
16
  """Fixture that returns a simple Pet Store API schema."""
17
  return {
18
  "openapi": "3.1.0",
@@ -109,7 +109,7 @@ def parsed_petstore_routes(petstore_schema):
109
 
110
 
111
  @pytest.fixture
112
- def bookstore_schema() -> Dict[str, Any]:
113
  """Fixture that returns a Book Store API schema with different parameter types."""
114
  return {
115
  "openapi": "3.1.0",
@@ -292,7 +292,7 @@ def fastapi_app() -> FastAPI:
292
 
293
 
294
  @pytest.fixture
295
- def fastapi_openapi_schema(fastapi_app) -> Dict[str, Any]:
296
  """Fixture that returns the OpenAPI schema of the FastAPI app."""
297
  return fastapi_app.openapi()
298
 
 
1
  """Tests for the OpenAPI parsing utilities."""
2
 
3
+ from typing import Any
4
 
5
  import pytest
6
  from fastapi import Body, FastAPI, Path, Query
 
12
 
13
 
14
  @pytest.fixture
15
+ def petstore_schema() -> dict[str, Any]:
16
  """Fixture that returns a simple Pet Store API schema."""
17
  return {
18
  "openapi": "3.1.0",
 
109
 
110
 
111
  @pytest.fixture
112
+ def bookstore_schema() -> dict[str, Any]:
113
  """Fixture that returns a Book Store API schema with different parameter types."""
114
  return {
115
  "openapi": "3.1.0",
 
292
 
293
 
294
  @pytest.fixture
295
+ def fastapi_openapi_schema(fastapi_app) -> dict[str, Any]:
296
  """Fixture that returns the OpenAPI schema of the FastAPI app."""
297
  return fastapi_app.openapi()
298
 
tests/utilities/openapi/test_openapi_advanced.py CHANGED
@@ -1,6 +1,6 @@
1
  """Tests for advanced features of the OpenAPI utilities."""
2
 
3
- from typing import Any, Dict
4
 
5
  import pytest
6
 
@@ -8,7 +8,7 @@ from fastmcp.utilities.openapi import parse_openapi_to_http_routes
8
 
9
 
10
  @pytest.fixture
11
- def complex_schema() -> Dict[str, Any]:
12
  """Fixture that returns a complex OpenAPI schema with nested references."""
13
  return {
14
  "openapi": "3.1.0",
@@ -167,7 +167,7 @@ def complex_route_map(parsed_complex_routes):
167
 
168
 
169
  @pytest.fixture
170
- def schema_with_invalid_reference() -> Dict[str, Any]:
171
  """Fixture that returns a schema with an invalid reference."""
172
  return {
173
  "openapi": "3.1.0",
@@ -191,7 +191,7 @@ def schema_with_invalid_reference() -> Dict[str, Any]:
191
 
192
 
193
  @pytest.fixture
194
- def schema_with_content_params() -> Dict[str, Any]:
195
  """Fixture that returns a schema with content-based parameters (complex parameters)."""
196
  return {
197
  "openapi": "3.1.0",
@@ -236,7 +236,7 @@ def parsed_content_param_routes(schema_with_content_params):
236
 
237
 
238
  @pytest.fixture
239
- def schema_all_http_methods() -> Dict[str, Any]:
240
  """Fixture that returns a schema with all HTTP methods."""
241
  return {
242
  "openapi": "3.1.0",
 
1
  """Tests for advanced features of the OpenAPI utilities."""
2
 
3
+ from typing import Any
4
 
5
  import pytest
6
 
 
8
 
9
 
10
  @pytest.fixture
11
+ def complex_schema() -> dict[str, Any]:
12
  """Fixture that returns a complex OpenAPI schema with nested references."""
13
  return {
14
  "openapi": "3.1.0",
 
167
 
168
 
169
  @pytest.fixture
170
+ def schema_with_invalid_reference() -> dict[str, Any]:
171
  """Fixture that returns a schema with an invalid reference."""
172
  return {
173
  "openapi": "3.1.0",
 
191
 
192
 
193
  @pytest.fixture
194
+ def schema_with_content_params() -> dict[str, Any]:
195
  """Fixture that returns a schema with content-based parameters (complex parameters)."""
196
  return {
197
  "openapi": "3.1.0",
 
236
 
237
 
238
  @pytest.fixture
239
+ def schema_all_http_methods() -> dict[str, Any]:
240
  """Fixture that returns a schema with all HTTP methods."""
241
  return {
242
  "openapi": "3.1.0",
tests/utilities/openapi/test_openapi_fastapi.py CHANGED
@@ -1,6 +1,6 @@
1
  """Tests for FastAPI integration with the OpenAPI utilities."""
2
 
3
- from typing import Any, Dict
4
 
5
  import pytest
6
  from fastapi import FastAPI
@@ -12,7 +12,6 @@ from fastmcp.utilities.openapi import parse_openapi_to_http_routes
12
  def fastapi_server() -> FastAPI:
13
  """Fixture that returns a FastAPI app for live OpenAPI schema testing."""
14
  from enum import Enum
15
- from typing import List
16
 
17
  from fastapi import Body, Depends, Header, HTTPException, Path, Query
18
  from pydantic import BaseModel, Field
@@ -145,7 +144,7 @@ def fastapi_server() -> FastAPI:
145
  )
146
  async def update_item_tags(
147
  item_id: int = Path(..., description="The ID of the item"),
148
- tags: List[str] = Body(..., description="Updated tags"),
149
  ):
150
  """Update just the tags of an item."""
151
  return {"item_id": item_id, "tags": tags}
@@ -229,7 +228,7 @@ def fastapi_server() -> FastAPI:
229
 
230
 
231
  @pytest.fixture
232
- def fastapi_openapi_schema(fastapi_server) -> Dict[str, Any]:
233
  """Fixture that returns the OpenAPI schema from a live FastAPI server."""
234
  return fastapi_server.openapi()
235
 
 
1
  """Tests for FastAPI integration with the OpenAPI utilities."""
2
 
3
+ from typing import Any
4
 
5
  import pytest
6
  from fastapi import FastAPI
 
12
  def fastapi_server() -> FastAPI:
13
  """Fixture that returns a FastAPI app for live OpenAPI schema testing."""
14
  from enum import Enum
 
15
 
16
  from fastapi import Body, Depends, Header, HTTPException, Path, Query
17
  from pydantic import BaseModel, Field
 
144
  )
145
  async def update_item_tags(
146
  item_id: int = Path(..., description="The ID of the item"),
147
+ tags: list[str] = Body(..., description="Updated tags"),
148
  ):
149
  """Update just the tags of an item."""
150
  return {"item_id": item_id, "tags": tags}
 
228
 
229
 
230
  @pytest.fixture
231
+ def fastapi_openapi_schema(fastapi_server) -> dict[str, Any]:
232
  """Fixture that returns the OpenAPI schema from a live FastAPI server."""
233
  return fastapi_server.openapi()
234
 
uv.lock CHANGED
@@ -1,4 +1,5 @@
1
  version = 1
 
2
  requires-python = ">=3.10"
3
 
4
  [[package]]
@@ -127,7 +128,7 @@ name = "click"
127
  version = "8.1.8"
128
  source = { registry = "https://pypi.org/simple" }
129
  dependencies = [
130
- { name = "colorama", marker = "platform_system == 'Windows'" },
131
  ]
132
  sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
133
  wheels = [
@@ -230,7 +231,7 @@ name = "fancycompleter"
230
  version = "0.9.1"
231
  source = { registry = "https://pypi.org/simple" }
232
  dependencies = [
233
- { name = "pyreadline", marker = "platform_system == 'Windows'" },
234
  { name = "pyrepl" },
235
  ]
236
  sdist = { url = "https://files.pythonhosted.org/packages/a9/95/649d135442d8ecf8af5c7e235550c628056423c96c4bc6787348bdae9248/fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272", size = 10866 }
@@ -254,7 +255,6 @@ wheels = [
254
 
255
  [[package]]
256
  name = "fastmcp"
257
- version = "0.4.2.dev40+g8720a78.d20250410"
258
  source = { editable = "." }
259
  dependencies = [
260
  { name = "dotenv" },
 
1
  version = 1
2
+ revision = 1
3
  requires-python = ">=3.10"
4
 
5
  [[package]]
 
128
  version = "8.1.8"
129
  source = { registry = "https://pypi.org/simple" }
130
  dependencies = [
131
+ { name = "colorama", marker = "sys_platform == 'win32'" },
132
  ]
133
  sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
134
  wheels = [
 
231
  version = "0.9.1"
232
  source = { registry = "https://pypi.org/simple" }
233
  dependencies = [
234
+ { name = "pyreadline", marker = "sys_platform == 'win32'" },
235
  { name = "pyrepl" },
236
  ]
237
  sdist = { url = "https://files.pythonhosted.org/packages/a9/95/649d135442d8ecf8af5c7e235550c628056423c96c4bc6787348bdae9248/fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272", size = 10866 }
 
255
 
256
  [[package]]
257
  name = "fastmcp"
 
258
  source = { editable = "." }
259
  dependencies = [
260
  { name = "dotenv" },