Fix parameterized type bug & better error message (#273)

## PR Description
### 1. Bug Fix
A bug was observed where tools that were using [parameterized
generics](https://docs.python.org/3/library/stdtypes.html#types-genericalias)
(e.g., `dict[str, Any]`) for their input parameters or output, then this
would cause the Worker to fail on startup with the error `issubclass()
arg 1 must be a class` for Python3.13+. This error would not occur for
Python versions less than 3.13.

In Python <3.13, parameterized generics would implicitly be treated as
their underlying type (its origin), so it was possible to treat
`dict[str, Any]` as a class and pass it to `issubclass`. This is not the
case for Python 3.13, so we need to explicitly strip the GenericAlias
(e.g., `dict[str, Any]`) down to its origin (e.g., `dict`), before
checking if it is a subclass of `Enum`.

### 2. Better Error Message
When a tool used an unsupported parameter/output type, then Arcade would
display the following message:
```
Failed to start Arcade Worker: 
Type error encountered while adding tool get_website_map from arcade_web.tools.firecrawl. 
Reason: issubclass() arg 1 must be a class
```

But now it displays
```
Failed to start Arcade Worker: 
Error encountered while adding tool get_website_map from arcade_web.tools.firecrawl. 
Reason: Unsupported type: tuple[str, str]
```
This commit is contained in:
Eric Gustin 2025-03-07 11:46:42 -08:00 committed by GitHub
parent fb69e9ef77
commit 4608cce862
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -253,6 +253,10 @@ class ToolCatalog(BaseModel):
raise ToolDefinitionError(
f"Type error encountered while adding tool {tool_name} from {module_name}. Reason: {e}"
)
except Exception as e:
raise ToolDefinitionError(
f"Error encountered while adding tool {tool_name} from {module_name}. Reason: {e}"
)
def __getitem__(self, name: FullyQualifiedName) -> MaterializedTool:
return self.get_tool(name)
@ -623,15 +627,18 @@ def get_wire_type_info(_type: type) -> WireTypeInfo:
type_to_check = inner_type if is_list else _type
# Strip generic parameters if type_to_check is a parameterized generic
actual_type = get_origin(type_to_check) or type_to_check
# Special case: Literal["string1", "string2"] can be enumerated on the wire
if is_string_literal(type_to_check):
is_enum = True
enum_values = [str(e) for e in get_args(type_to_check)]
# Special case: Enum can be enumerated on the wire
elif issubclass(type_to_check, Enum):
elif issubclass(actual_type, Enum):
is_enum = True
enum_values = [e.value for e in type_to_check] # type: ignore[union-attr]
enum_values = [e.value for e in actual_type] # type: ignore[union-attr]
return WireTypeInfo(wire_type, inner_wire_type, enum_values if is_enum else None)
@ -732,10 +739,10 @@ def get_wire_type(
if wire_type:
return wire_type
if issubclass(_type, Enum):
if isinstance(_type, type) and issubclass(_type, Enum):
return "string"
if issubclass(_type, BaseModel):
if isinstance(_type, type) and issubclass(_type, BaseModel):
return "json"
raise ToolDefinitionError(f"Unsupported parameter type: {_type}")