core: Add ruff rules FBT001 and FBT002 (#30695)

Add ruff rules
[FBT001](https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument/)
and
[FBT002](https://docs.astral.sh/ruff/rules/boolean-default-value-positional-argument/).
Mostly `noqa`s to not introduce breaking changes and possible
non-breaking fixes have already been done in a [previous
PR](https://github.com/langchain-ai/langchain/pull/29424).
These rules will prevent new violations to happen.
This commit is contained in:
Christophe Bornet 2025-04-11 22:26:33 +02:00 committed by GitHub
parent 2803a48661
commit 913c896598
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 96 additions and 47 deletions

View File

@ -988,7 +988,11 @@ class BaseCallbackManager(CallbackManagerMixin):
"""Whether the callback manager is async."""
return False
def add_handler(self, handler: BaseCallbackHandler, inherit: bool = True) -> None:
def add_handler(
self,
handler: BaseCallbackHandler,
inherit: bool = True, # noqa: FBT001,FBT002
) -> None:
"""Add a handler to the callback manager.
Args:
@ -1012,7 +1016,9 @@ class BaseCallbackManager(CallbackManagerMixin):
self.inheritable_handlers.remove(handler)
def set_handlers(
self, handlers: list[BaseCallbackHandler], inherit: bool = True
self,
handlers: list[BaseCallbackHandler],
inherit: bool = True, # noqa: FBT001,FBT002
) -> None:
"""Set handlers as the only handlers on the callback manager.
@ -1025,7 +1031,11 @@ class BaseCallbackManager(CallbackManagerMixin):
for handler in handlers:
self.add_handler(handler, inherit=inherit)
def set_handler(self, handler: BaseCallbackHandler, inherit: bool = True) -> None:
def set_handler(
self,
handler: BaseCallbackHandler,
inherit: bool = True, # noqa: FBT001,FBT002
) -> None:
"""Set handler as the only handler on the callback manager.
Args:
@ -1034,7 +1044,11 @@ class BaseCallbackManager(CallbackManagerMixin):
"""
self.set_handlers([handler], inherit=inherit)
def add_tags(self, tags: list[str], inherit: bool = True) -> None:
def add_tags(
self,
tags: list[str],
inherit: bool = True, # noqa: FBT001,FBT002
) -> None:
"""Add tags to the callback manager.
Args:
@ -1058,7 +1072,11 @@ class BaseCallbackManager(CallbackManagerMixin):
self.tags.remove(tag)
self.inheritable_tags.remove(tag)
def add_metadata(self, metadata: dict[str, Any], inherit: bool = True) -> None:
def add_metadata(
self,
metadata: dict[str, Any],
inherit: bool = True, # noqa: FBT001,FBT002
) -> None:
"""Add metadata to the callback manager.
Args:

View File

@ -1547,7 +1547,7 @@ class CallbackManager(BaseCallbackManager):
cls,
inheritable_callbacks: Callbacks = None,
local_callbacks: Callbacks = None,
verbose: bool = False,
verbose: bool = False, # noqa: FBT001,FBT002
inheritable_tags: Optional[list[str]] = None,
local_tags: Optional[list[str]] = None,
inheritable_metadata: Optional[dict[str, Any]] = None,
@ -2073,7 +2073,7 @@ class AsyncCallbackManager(BaseCallbackManager):
cls,
inheritable_callbacks: Callbacks = None,
local_callbacks: Callbacks = None,
verbose: bool = False,
verbose: bool = False, # noqa: FBT001,FBT002
inheritable_tags: Optional[list[str]] = None,
local_tags: Optional[list[str]] = None,
inheritable_metadata: Optional[dict[str, Any]] = None,

View File

@ -26,7 +26,7 @@ class OutputParserException(ValueError, LangChainException): # noqa: N818
error: Any,
observation: Optional[str] = None,
llm_output: Optional[str] = None,
send_to_llm: bool = False,
send_to_llm: bool = False, # noqa: FBT001,FBT002
):
"""Create an OutputParserException.

View File

@ -16,7 +16,7 @@ _debug: bool = False
_llm_cache: Optional["BaseCache"] = None
def set_verbose(value: bool) -> None:
def set_verbose(value: bool) -> None: # noqa: FBT001
"""Set a new value for the `verbose` global setting.
Args:
@ -89,7 +89,7 @@ def get_verbose() -> bool:
return _verbose or old_verbose
def set_debug(value: bool) -> None:
def set_debug(value: bool) -> None: # noqa: FBT001
"""Set a new value for the `debug` global setting.
Args:

View File

@ -52,7 +52,7 @@ class Reviver:
self,
secrets_map: Optional[dict[str, str]] = None,
valid_namespaces: Optional[list[str]] = None,
secrets_from_env: bool = True,
secrets_from_env: bool = True, # noqa: FBT001,FBT002
additional_import_mappings: Optional[
dict[tuple[str, ...], tuple[str, ...]]
] = None,

View File

@ -235,6 +235,7 @@ class AIMessage(BaseMessage):
return values
@override
def pretty_repr(self, html: bool = False) -> str:
"""Return a pretty representation of the message.

View File

@ -122,7 +122,10 @@ class BaseMessage(Serializable):
prompt = ChatPromptTemplate(messages=[self]) # type: ignore[call-arg]
return prompt + other
def pretty_repr(self, html: bool = False) -> str:
def pretty_repr(
self,
html: bool = False, # noqa: FBT001,FBT002
) -> str:
"""Get a pretty representation of the message.
Args:

View File

@ -102,7 +102,10 @@ class BaseMessagePromptTemplate(Serializable, ABC):
List of input variables.
"""
def pretty_repr(self, html: bool = False) -> str:
def pretty_repr(
self,
html: bool = False, # noqa: FBT001,FBT002
) -> str:
"""Human-readable representation.
Args:
@ -270,6 +273,7 @@ class MessagesPlaceholder(BaseMessagePromptTemplate):
"""
return [self.variable_name] if not self.optional else []
@override
def pretty_repr(self, html: bool = False) -> str:
"""Human-readable representation.
@ -406,6 +410,7 @@ class BaseStringMessagePromptTemplate(BaseMessagePromptTemplate, ABC):
"""
return self.prompt.input_variables
@override
def pretty_repr(self, html: bool = False) -> str:
"""Human-readable representation.
@ -675,6 +680,7 @@ class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):
content=content, additional_kwargs=self.additional_kwargs
)
@override
def pretty_repr(self, html: bool = False) -> str:
"""Human-readable representation.
@ -777,7 +783,10 @@ class BaseChatPromptTemplate(BasePromptTemplate, ABC):
"""Async format kwargs into a list of messages."""
return self.format_messages(**kwargs)
def pretty_repr(self, html: bool = False) -> str:
def pretty_repr(
self,
html: bool = False, # noqa: FBT001,FBT002
) -> str:
"""Human-readable representation.
Args:
@ -1331,6 +1340,7 @@ class ChatPromptTemplate(BaseChatPromptTemplate):
"""
raise NotImplementedError
@override
def pretty_repr(self, html: bool = False) -> str:
"""Human-readable representation.

View File

@ -10,6 +10,7 @@ from pydantic import (
Field,
model_validator,
)
from typing_extensions import override
from langchain_core.example_selectors import BaseExampleSelector
from langchain_core.messages import BaseMessage, get_buffer_string
@ -453,6 +454,7 @@ class FewShotChatMessagePromptTemplate(
messages = await self.aformat_messages(**kwargs)
return get_buffer_string(messages)
@override
def pretty_repr(self, html: bool = False) -> str:
"""Return a pretty representation of the prompt template.

View File

@ -133,7 +133,10 @@ class ImagePromptTemplate(BasePromptTemplate[ImageURL]):
"""
return await run_in_executor(None, self.format, **kwargs)
def pretty_repr(self, html: bool = False) -> str:
def pretty_repr(
self,
html: bool = False, # noqa: FBT001,FBT002
) -> str:
"""Return a pretty representation of the prompt.
Args:

View File

@ -293,7 +293,10 @@ class StringPromptTemplate(BasePromptTemplate, ABC):
"""
return StringPromptValue(text=await self.aformat(**kwargs))
def pretty_repr(self, html: bool = False) -> str:
def pretty_repr(
self,
html: bool = False, # noqa: FBT001,FBT002
) -> str:
"""Get a pretty representation of the prompt.
Args:

View File

@ -359,7 +359,7 @@ class Graph:
source: Node,
target: Node,
data: Optional[Stringifiable] = None,
conditional: bool = False,
conditional: bool = False, # noqa: FBT001,FBT002
) -> Edge:
"""Add an edge to the graph and return it.

View File

@ -104,7 +104,7 @@ class PngDrawer:
source: str,
target: str,
label: Optional[str] = None,
conditional: bool = False,
conditional: bool = False, # noqa: FBT001,FBT002
) -> None:
"""Adds an edge to the graph.

View File

@ -142,7 +142,7 @@ class Tool(BaseTool):
func: Optional[Callable],
name: str, # We keep these required to support backwards compatibility
description: str,
return_direct: bool = False,
return_direct: bool = False, # noqa: FBT001,FBT002
args_schema: Optional[ArgsSchema] = None,
coroutine: Optional[
Callable[..., Awaitable[Any]]

View File

@ -122,9 +122,9 @@ class StructuredTool(BaseTool):
coroutine: Optional[Callable[..., Awaitable[Any]]] = None,
name: Optional[str] = None,
description: Optional[str] = None,
return_direct: bool = False,
return_direct: bool = False, # noqa: FBT001,FBT002
args_schema: Optional[ArgsSchema] = None,
infer_schema: bool = True,
infer_schema: bool = True, # noqa: FBT001,FBT002
*,
response_format: Literal["content", "content_and_artifact"] = "content",
parse_docstring: bool = False,

View File

@ -186,7 +186,7 @@ _configure_hooks: list[
def register_configure_hook(
context_var: ContextVar[Optional[Any]],
inheritable: bool,
inheritable: bool, # noqa: FBT001
handle_class: Optional[type[BaseCallbackHandler]] = None,
env_var: Optional[str] = None,
) -> None:

View File

@ -62,7 +62,7 @@ class EvaluatorCallbackHandler(BaseTracer):
evaluators: Sequence[langsmith.RunEvaluator],
client: Optional[langsmith.Client] = None,
example_id: Optional[Union[UUID, str]] = None,
skip_unfinished: bool = True,
skip_unfinished: bool = True, # noqa: FBT001,FBT002
project_name: Optional[str] = "evaluators",
max_concurrency: Optional[int] = None,
**kwargs: Any,

View File

@ -68,7 +68,7 @@ def grab_literal(template: str, l_del: str) -> tuple[str, str]:
def l_sa_check(
template: str, # noqa: ARG001
literal: str,
is_standalone: bool,
is_standalone: bool, # noqa: FBT001
) -> bool:
"""Do a preliminary check to see if a tag could be a standalone.
@ -91,7 +91,11 @@ def l_sa_check(
return False
def r_sa_check(template: str, tag_type: str, is_standalone: bool) -> bool:
def r_sa_check(
template: str,
tag_type: str,
is_standalone: bool, # noqa: FBT001
) -> bool:
"""Do a final check to see if a tag could be a standalone.
Args:
@ -422,8 +426,8 @@ def render(
def_ldel: str = "{{",
def_rdel: str = "}}",
scopes: Optional[Scopes] = None,
warn: bool = False,
keep: bool = False,
warn: bool = False, # noqa: FBT001,FBT002
keep: bool = False, # noqa: FBT001,FBT002
) -> str:
"""Render a mustache template.

View File

@ -420,7 +420,7 @@ def _create_root_model(
def schema(
cls: type[BaseModel],
by_alias: bool = True,
by_alias: bool = True, # noqa: FBT001,FBT002
ref_template: str = DEFAULT_REF_TEMPLATE,
) -> dict[str, Any]:
# Complains about schema not being defined in superclass
@ -432,7 +432,7 @@ def _create_root_model(
def model_json_schema(
cls: type[BaseModel],
by_alias: bool = True,
by_alias: bool = True, # noqa: FBT001,FBT002
ref_template: str = DEFAULT_REF_TEMPLATE,
schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
mode: JsonSchemaMode = "validation",

View File

@ -100,8 +100,6 @@ ignore = [
"ANN401",
"BLE",
"ERA",
"FBT001",
"FBT002",
"PLR2004",
"RUF",
"SLF",

View File

@ -105,7 +105,7 @@ def _remove_additionalproperties_from_untyped_dicts(schema: dict) -> dict[str, A
"""
def _remove_dict_additional_props(
obj: Union[dict[str, Any], list[Any]], inside_properties: bool = False
obj: Union[dict[str, Any], list[Any]], *, inside_properties: bool = False
) -> None:
if isinstance(obj, dict):
if (
@ -120,11 +120,13 @@ def _remove_additionalproperties_from_untyped_dicts(schema: dict) -> dict[str, A
# We are "inside_properties" if the *current* key is "properties",
# or if we were already inside properties in the caller.
next_inside_properties = inside_properties or (key == "properties")
_remove_dict_additional_props(value, next_inside_properties)
_remove_dict_additional_props(
value, inside_properties=next_inside_properties
)
elif isinstance(obj, list):
for item in obj:
_remove_dict_additional_props(item, inside_properties)
_remove_dict_additional_props(item, inside_properties=inside_properties)
_remove_dict_additional_props(schema, inside_properties=False)
return schema

View File

@ -119,10 +119,10 @@ class _MockStructuredTool(BaseTool):
args_schema: type[BaseModel] = _MockSchema
description: str = "A Structured Tool"
def _run(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
def _run(self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
return f"{arg1} {arg2} {arg3}"
async def _arun(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
async def _arun(self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
raise NotImplementedError
@ -146,11 +146,13 @@ def test_misannotated_base_tool_raises_error() -> None:
args_schema: BaseModel = _MockSchema # type: ignore[assignment]
description: str = "A Structured Tool"
def _run(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
def _run(
self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None
) -> str:
return f"{arg1} {arg2} {arg3}"
async def _arun(
self, arg1: int, arg2: bool, arg3: Optional[dict] = None
self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None
) -> str:
raise NotImplementedError
@ -163,11 +165,11 @@ def test_forward_ref_annotated_base_tool_accepted() -> None:
args_schema: "type[BaseModel]" = _MockSchema
description: str = "A Structured Tool"
def _run(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
def _run(self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
return f"{arg1} {arg2} {arg3}"
async def _arun(
self, arg1: int, arg2: bool, arg3: Optional[dict] = None
self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None
) -> str:
raise NotImplementedError
@ -180,11 +182,11 @@ def test_subclass_annotated_base_tool_accepted() -> None:
args_schema: type[_MockSchema] = _MockSchema
description: str = "A Structured Tool"
def _run(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
def _run(self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
return f"{arg1} {arg2} {arg3}"
async def _arun(
self, arg1: int, arg2: bool, arg3: Optional[dict] = None
self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None
) -> str:
raise NotImplementedError
@ -197,14 +199,14 @@ def test_decorator_with_specified_schema() -> None:
"""Test that manually specified schemata are passed through to the tool."""
@tool(args_schema=_MockSchema)
def tool_func(arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
def tool_func(*, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
return f"{arg1} {arg2} {arg3}"
assert isinstance(tool_func, BaseTool)
assert tool_func.args_schema == _MockSchema
@tool(args_schema=cast("ArgsSchema", _MockSchemaV1))
def tool_func_v1(arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
def tool_func_v1(*, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
return f"{arg1} {arg2} {arg3}"
assert isinstance(tool_func_v1, BaseTool)
@ -216,7 +218,7 @@ def test_decorated_function_schema_equivalent() -> None:
@tool
def structured_tool_input(
arg1: int, arg2: bool, arg3: Optional[dict] = None
*, arg1: int, arg2: bool, arg3: Optional[dict] = None
) -> str:
"""Return the arguments directly."""
return f"{arg1} {arg2} {arg3}"
@ -1397,14 +1399,17 @@ class _MockStructuredToolWithRawOutput(BaseTool):
response_format: Literal["content_and_artifact"] = "content_and_artifact"
def _run(
self, arg1: int, arg2: bool, arg3: Optional[dict] = None
self,
arg1: int,
arg2: bool, # noqa: FBT001
arg3: Optional[dict] = None,
) -> tuple[str, dict]:
return f"{arg1} {arg2}", {"arg1": arg1, "arg2": arg2, "arg3": arg3}
@tool("structured_api", response_format="content_and_artifact")
def _mock_structured_tool_with_artifact(
arg1: int, arg2: bool, arg3: Optional[dict] = None
*, arg1: int, arg2: bool, arg3: Optional[dict] = None
) -> tuple[str, dict]:
"""A Structured Tool."""
return f"{arg1} {arg2}", {"arg1": arg1, "arg2": arg2, "arg3": arg3}