Skip to content

Commit 735bc50

Browse files
committed
refactor: replace griffe with zero-dependency regex parser
Replace the griffe library with simple regex patterns for parsing Google, NumPy, and Sphinx-style docstrings. This eliminates the new runtime dependency while maintaining full functionality. All 42 tests continue to pass. Github-Issue:#226
1 parent 8127f1e commit 735bc50

File tree

3 files changed

+44
-102
lines changed

3 files changed

+44
-102
lines changed

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ classifiers = [
2626
]
2727
dependencies = [
2828
"anyio>=4.9",
29-
"griffe>=1.5.0",
3029
"httpx>=0.27.1",
3130
"httpx-sse>=0.4",
3231
"pydantic>=2.12.0",

src/mcp/server/mcpserver/utilities/func_metadata.py

Lines changed: 44 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import functools
22
import inspect
33
import json
4-
import logging
54
import re
6-
from collections.abc import Awaitable, Callable, Iterator, Sequence
7-
from contextlib import contextmanager
5+
from collections.abc import Awaitable, Callable, Sequence
86
from itertools import chain
97
from types import GenericAlias
10-
from typing import Annotated, Any, Literal, cast, get_args, get_origin, get_type_hints
8+
from typing import Annotated, Any, cast, get_args, get_origin, get_type_hints
119

1210
import anyio
1311
import anyio.to_thread
1412
import pydantic_core
15-
from griffe import Docstring, DocstringSectionKind
1613
from pydantic import BaseModel, ConfigDict, Field, WithJsonSchema, create_model
1714
from pydantic.fields import FieldInfo
1815
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaWarningKind
@@ -171,96 +168,35 @@ def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]:
171168
)
172169

173170

174-
_DocstringStyle = Literal["google", "numpy", "sphinx"]
175-
176-
# Patterns to infer docstring style, adapted from pydantic-ai.
177-
# Each entry is (pattern_template, replacement_keywords, style).
178-
_DOCSTRING_STYLE_PATTERNS: list[tuple[str, list[str], _DocstringStyle]] = [
179-
(
180-
r"\n[ \t]*:{0}([ \t]+\w+)*:([ \t]+.+)?\n",
181-
[
182-
"param",
183-
"parameter",
184-
"arg",
185-
"argument",
186-
"key",
187-
"keyword",
188-
"type",
189-
"var",
190-
"ivar",
191-
"cvar",
192-
"vartype",
193-
"returns",
194-
"return",
195-
"rtype",
196-
"raises",
197-
"raise",
198-
"except",
199-
"exception",
200-
],
201-
"sphinx",
202-
),
203-
(
204-
r"\n[ \t]*{0}:([ \t]+.+)?\n[ \t]+.+",
205-
[
206-
"args",
207-
"arguments",
208-
"params",
209-
"parameters",
210-
"keyword args",
211-
"keyword arguments",
212-
"raises",
213-
"exceptions",
214-
"returns",
215-
"yields",
216-
"receives",
217-
"examples",
218-
"attributes",
219-
],
220-
"google",
221-
),
222-
(
223-
r"\n[ \t]*{0}\n[ \t]*---+\n",
224-
[
225-
"deprecated",
226-
"parameters",
227-
"other parameters",
228-
"returns",
229-
"yields",
230-
"receives",
231-
"raises",
232-
"warns",
233-
"attributes",
234-
],
235-
"numpy",
236-
),
237-
]
238-
239-
240-
def _infer_docstring_style(doc: str) -> _DocstringStyle:
241-
"""Infer the docstring style from its content."""
242-
for pattern, replacements, style in _DOCSTRING_STYLE_PATTERNS:
243-
matches = (
244-
re.search(pattern.format(replacement), doc, re.IGNORECASE | re.MULTILINE) for replacement in replacements
245-
)
246-
if any(matches):
247-
return style
248-
return "google"
249-
250-
251-
@contextmanager
252-
def _suppress_griffe_logging() -> Iterator[None]:
253-
"""Temporarily suppress griffe's verbose logging."""
254-
old_level = logging.root.getEffectiveLevel()
255-
logging.root.setLevel(logging.ERROR)
256-
yield
257-
logging.root.setLevel(old_level)
171+
# Regex patterns for extracting parameter descriptions from docstrings.
172+
# Supports Google, NumPy, and Sphinx styles without any external dependencies.
173+
_GOOGLE_ARGS_RE = re.compile(
174+
r"(?:Args|Arguments|Parameters)\s*:\s*\n((?:[ \t]+.+\n?)+)",
175+
re.IGNORECASE,
176+
)
177+
_GOOGLE_PARAM_RE = re.compile(
178+
r"^[ \t]+(\w+)\s*(?:\(.+?\))?\s*:\s*(.+(?:\n(?:[ \t]+(?![ \t]*\w+\s*(?:\(.+?\))?\s*:).+))*)",
179+
re.MULTILINE,
180+
)
181+
_SPHINX_PARAM_RE = re.compile(
182+
r":param\s+(\w+)\s*:\s*(.+(?:\n(?:[ \t]+(?!:).+))*)",
183+
re.MULTILINE,
184+
)
185+
_NUMPY_PARAMS_RE = re.compile(
186+
r"(?:Parameters)\s*\n\s*-{3,}\s*\n((?:.*\n?)+?)(?:\n\s*\w+\s*\n\s*-{3,}|\Z)",
187+
re.IGNORECASE,
188+
)
189+
_NUMPY_PARAM_RE = re.compile(
190+
r"^(\w+)\s*(?::.*)?$\n((?:[ \t]+.+\n?)+)",
191+
re.MULTILINE,
192+
)
258193

259194

260195
def _parse_docstring_params(func: Callable[..., Any]) -> dict[str, str]:
261196
"""Parse a function's docstring to extract parameter descriptions.
262197
263-
Supports Google, NumPy, and Sphinx-style docstrings with automatic format detection.
198+
Supports Google, NumPy, and Sphinx-style docstrings using simple regex patterns.
199+
No external dependencies required.
264200
265201
Returns:
266202
A dict mapping parameter names to their descriptions.
@@ -269,15 +205,24 @@ def _parse_docstring_params(func: Callable[..., Any]) -> dict[str, str]:
269205
if not doc:
270206
return {}
271207

272-
docstring_style = _infer_docstring_style(doc)
273-
docstring = Docstring(doc, lineno=1, parser=docstring_style)
274-
275-
with _suppress_griffe_logging():
276-
sections = docstring.parse()
277-
278-
for section in sections:
279-
if section.kind == DocstringSectionKind.parameters:
280-
return {p.name: p.description for p in section.value}
208+
# Try Sphinx style first (:param name: description)
209+
sphinx_matches = _SPHINX_PARAM_RE.findall(doc)
210+
if sphinx_matches:
211+
return {name: " ".join(desc.split()) for name, desc in sphinx_matches}
212+
213+
# Try Google style (Args: / Arguments: / Parameters:)
214+
google_section = _GOOGLE_ARGS_RE.search(doc)
215+
if google_section:
216+
params = _GOOGLE_PARAM_RE.findall(google_section.group(1))
217+
if params:
218+
return {name: " ".join(desc.split()) for name, desc in params}
219+
220+
# Try NumPy style (Parameters\n----------)
221+
numpy_section = _NUMPY_PARAMS_RE.search(doc)
222+
if numpy_section:
223+
params = _NUMPY_PARAM_RE.findall(numpy_section.group(1))
224+
if params:
225+
return {name: " ".join(desc.split()) for name, desc in params}
281226

282227
return {}
283228

uv.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)