diff --git a/arcade/arcade/templates/{{ toolkit_name }}/.ruff.toml b/arcade/arcade/templates/{{ toolkit_name }}/.ruff.toml index d745a6e6..421edfcf 100644 --- a/arcade/arcade/templates/{{ toolkit_name }}/.ruff.toml +++ b/arcade/arcade/templates/{{ toolkit_name }}/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/arcade/arcade/templates/{{ toolkit_name }}/Makefile b/arcade/arcade/templates/{{ toolkit_name }}/Makefile index 4c1eb724..78d525d3 100644 --- a/arcade/arcade/templates/{{ toolkit_name }}/Makefile +++ b/arcade/arcade/templates/{{ toolkit_name }}/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/arcade/arcade/templates/{{ toolkit_name }}/pyproject.toml b/arcade/arcade/templates/{{ toolkit_name }}/pyproject.toml index 07b216a0..b0295a38 100644 --- a/arcade/arcade/templates/{{ toolkit_name }}/pyproject.toml +++ b/arcade/arcade/templates/{{ toolkit_name }}/pyproject.toml @@ -17,7 +17,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/contrib/langchain/Makefile b/contrib/langchain/Makefile index e83d90d4..fb7a4773 100644 --- a/contrib/langchain/Makefile +++ b/contrib/langchain/Makefile @@ -3,7 +3,7 @@ VERSION ?= "0.1.0" .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/code_sandbox/.ruff.toml b/toolkits/code_sandbox/.ruff.toml index bb64590d..19364180 100644 --- a/toolkits/code_sandbox/.ruff.toml +++ b/toolkits/code_sandbox/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/code_sandbox/Makefile b/toolkits/code_sandbox/Makefile index 3ded2851..b4222577 100644 --- a/toolkits/code_sandbox/Makefile +++ b/toolkits/code_sandbox/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/code_sandbox/pyproject.toml b/toolkits/code_sandbox/pyproject.toml index 1c12516c..769cc6f5 100644 --- a/toolkits/code_sandbox/pyproject.toml +++ b/toolkits/code_sandbox/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_code_sandbox" version = "1.0.0" -description = "LLM tools for running code in a sandbox" +description = "Arcade.dev LLM tools for running code in a sandbox" authors = ["Arcade "] [tool.poetry.dependencies] @@ -20,7 +20,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/github/.ruff.toml b/toolkits/github/.ruff.toml index bacd9161..f1aed90f 100644 --- a/toolkits/github/.ruff.toml +++ b/toolkits/github/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/github/Makefile b/toolkits/github/Makefile index df42f6d8..ce6a89a9 100644 --- a/toolkits/github/Makefile +++ b/toolkits/github/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/github/arcade_github/tools/activity.py b/toolkits/github/arcade_github/tools/activity.py index b944b928..ba5bf4a7 100644 --- a/toolkits/github/arcade_github/tools/activity.py +++ b/toolkits/github/arcade_github/tools/activity.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import httpx from arcade.sdk import ToolContext, tool @@ -50,7 +50,7 @@ async def list_stargazers( owner: Annotated[str, "The owner of the repository"], repo: Annotated[str, "The name of the repository"], limit: Annotated[ - Optional[int], + int | None, "The maximum number of stargazers to return. " "If not provided, all stargazers will be returned.", ] = None, diff --git a/toolkits/github/arcade_github/tools/issues.py b/toolkits/github/arcade_github/tools/issues.py index 090f58d9..74b99664 100644 --- a/toolkits/github/arcade_github/tools/issues.py +++ b/toolkits/github/arcade_github/tools/issues.py @@ -1,5 +1,5 @@ import json -from typing import Annotated, Optional +from typing import Annotated import httpx from arcade.sdk import ToolContext, tool @@ -27,12 +27,12 @@ async def create_issue( "The name of the repository without the .git extension. The name is not case sensitive.", ], title: Annotated[str, "The title of the issue."], - body: Annotated[Optional[str], "The contents of the issue."] = None, - assignees: Annotated[Optional[list[str]], "Logins for Users to assign to this issue."] = None, + body: Annotated[str | None, "The contents of the issue."] = None, + assignees: Annotated[list[str] | None, "Logins for Users to assign to this issue."] = None, milestone: Annotated[ - Optional[int], "The number of the milestone to associate this issue with." + int | None, "The number of the milestone to associate this issue with." ] = None, - labels: Annotated[Optional[list[str]], "Labels to associate with this issue."] = None, + labels: Annotated[list[str] | None, "Labels to associate with this issue."] = None, include_extra_data: Annotated[ bool, "If true, return all the data available about the pull requests. " diff --git a/toolkits/github/arcade_github/tools/pull_requests.py b/toolkits/github/arcade_github/tools/pull_requests.py index e35d718e..98f329bd 100644 --- a/toolkits/github/arcade_github/tools/pull_requests.py +++ b/toolkits/github/arcade_github/tools/pull_requests.py @@ -1,5 +1,5 @@ import json -from typing import Annotated, Optional +from typing import Annotated import httpx from arcade.sdk import ToolContext, tool @@ -38,17 +38,17 @@ async def list_pull_requests( str, "The name of the repository without the .git extension. The name is not case sensitive.", ], - state: Annotated[Optional[PRState], "The state of the pull requests to return."] = PRState.OPEN, + state: Annotated[PRState | None, "The state of the pull requests to return."] = PRState.OPEN, head: Annotated[ - Optional[str], + str | None, "Filter pulls by head user or head organization and branch name in the format of " "user:ref-name or organization:ref-name.", ] = None, - base: Annotated[Optional[str], "Filter pulls by base branch name."] = "main", + base: Annotated[str | None, "Filter pulls by base branch name."] = "main", sort: Annotated[ - Optional[PRSortProperty], "The property to sort the results by." + PRSortProperty | None, "The property to sort the results by." ] = PRSortProperty.CREATED, - direction: Annotated[Optional[SortDirection], "The direction of the sort."] = None, + direction: Annotated[SortDirection | None, "The direction of the sort."] = None, per_page: Annotated[int, "The number of results per page (max 100)."] = 30, page: Annotated[int, "The page number of the results to fetch."] = 1, include_extra_data: Annotated[ @@ -120,11 +120,11 @@ async def get_pull_request( ], pull_number: Annotated[int, "The number that identifies the pull request."], include_diff_content: Annotated[ - Optional[bool], + bool | None, "If true, return the diff content of the pull request.", ] = False, include_extra_data: Annotated[ - Optional[bool], + bool | None, "If true, return all the data available about the pull requests. " "This is a large payload and may impact performance - use with caution.", ] = False, @@ -201,16 +201,12 @@ async def update_pull_request( "The name of the repository without the .git extension. The name is not case sensitive.", ], pull_number: Annotated[int, "The number that identifies the pull request."], - title: Annotated[Optional[str], "The title of the pull request."] = None, - body: Annotated[Optional[str], "The contents of the pull request."] = None, - state: Annotated[ - Optional[PRState], "State of this Pull Request. Either open or closed." - ] = None, - base: Annotated[ - Optional[str], "The name of the branch you want your changes pulled into." - ] = None, + title: Annotated[str | None, "The title of the pull request."] = None, + body: Annotated[str | None, "The contents of the pull request."] = None, + state: Annotated[PRState | None, "State of this Pull Request. Either open or closed."] = None, + base: Annotated[str | None, "The name of the branch you want your changes pulled into."] = None, maintainer_can_modify: Annotated[ - Optional[bool], "Indicates whether maintainers can modify the pull request." + bool | None, "Indicates whether maintainers can modify the pull request." ] = None, ) -> Annotated[str, "JSON string containing updated information about the pull request"]: """ @@ -398,14 +394,14 @@ async def list_review_comments_on_pull_request( ], pull_number: Annotated[int, "The number that identifies the pull request."], sort: Annotated[ - Optional[ReviewCommentSortProperty], + ReviewCommentSortProperty | None, "The property to sort the results by. Can be one of: created, updated.", ] = ReviewCommentSortProperty.CREATED, direction: Annotated[ - Optional[SortDirection], "The direction to sort results. Can be one of: asc, desc." + SortDirection | None, "The direction to sort results. Can be one of: asc, desc." ] = SortDirection.DESC, since: Annotated[ - Optional[str], + str | None, "Only show results that were last updated after the given time. " "This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.", ] = None, @@ -494,31 +490,31 @@ async def create_review_comment( body: Annotated[str, "The text of the review comment."], path: Annotated[str, "The relative path to the file that necessitates a comment."], commit_id: Annotated[ - Optional[str], + str | None, "The SHA of the commit needing a comment. If not provided, the latest commit SHA of the " "PR's base branch will be used.", ] = None, start_line: Annotated[ - Optional[int], + int | None, "The start line of the range of lines in the pull request diff that the " "comment applies to. Required unless 'subject_type' is 'file'.", ] = None, end_line: Annotated[ - Optional[int], + int | None, "The end line of the range of lines in the pull request diff that the " "comment applies to. Required unless 'subject_type' is 'file'.", ] = None, side: Annotated[ - Optional[DiffSide], + DiffSide | None, "The side of the diff that the pull request's changes appear on. " "Use LEFT for deletions that appear in red. Use RIGHT for additions that appear in green " "or unchanged lines that appear in white and are shown for context", ] = DiffSide.RIGHT, start_side: Annotated[ - Optional[str], "The starting side of the diff that the comment applies to." + str | None, "The starting side of the diff that the comment applies to." ] = None, subject_type: Annotated[ - Optional[ReviewCommentSubjectType], + ReviewCommentSubjectType | None, "The type of subject that the comment applies to. Can be one of: file, hunk, or line.", ] = ReviewCommentSubjectType.FILE, include_extra_data: Annotated[ diff --git a/toolkits/github/arcade_github/tools/repositories.py b/toolkits/github/arcade_github/tools/repositories.py index 8e879f79..ead44fdf 100644 --- a/toolkits/github/arcade_github/tools/repositories.py +++ b/toolkits/github/arcade_github/tools/repositories.py @@ -1,5 +1,5 @@ import json -from typing import Annotated, Optional +from typing import Annotated import httpx from arcade.sdk import ToolContext, tool @@ -191,28 +191,28 @@ async def list_repository_activities( "The name of the repository without the .git extension. The name is not case sensitive.", ], direction: Annotated[ - Optional[SortDirection], "The direction to sort the results by." + SortDirection | None, "The direction to sort the results by." ] = SortDirection.DESC, per_page: Annotated[int, "The number of results per page (max 100)."] = 30, before: Annotated[ - Optional[str], + str | None, "A cursor (unique ID, e.g., a SHA of a commit) to search for results before this cursor.", ] = None, after: Annotated[ - Optional[str], + str | None, "A cursor (unique ID, e.g., a SHA of a commit) to search for results after this cursor.", ] = None, ref: Annotated[ - Optional[str], + str | None, "The Git reference for the activities you want to list. The ref for a branch can be " "formatted either as refs/heads/BRANCH_NAME or BRANCH_NAME, where BRANCH_NAME is the name " "of your branch.", ] = None, actor: Annotated[ - Optional[str], "The GitHub username to filter by the actor who performed the activity." + str | None, "The GitHub username to filter by the actor who performed the activity." ] = None, - time_period: Annotated[Optional[RepoTimePeriod], "The time period to filter by."] = None, - activity_type: Annotated[Optional[ActivityType], "The activity type to filter by."] = None, + time_period: Annotated[RepoTimePeriod | None, "The time period to filter by."] = None, + activity_type: Annotated[ActivityType | None, "The activity type to filter by."] = None, include_extra_data: Annotated[ bool, "If true, return all the data available about the repository activities. " @@ -293,14 +293,14 @@ async def list_review_comments_in_a_repository( "The name of the repository without the .git extension. The name is not case sensitive.", ], sort: Annotated[ - Optional[ReviewCommentSortProperty], "Can be one of: created, updated." + ReviewCommentSortProperty | None, "Can be one of: created, updated." ] = ReviewCommentSortProperty.CREATED, direction: Annotated[ - Optional[SortDirection], + SortDirection | None, "The direction to sort results. Ignored without sort parameter. Can be one of: asc, desc.", ] = SortDirection.DESC, since: Annotated[ - Optional[str], + str | None, "Only show results that were last updated after the given time. " "This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.", ] = None, diff --git a/toolkits/github/pyproject.toml b/toolkits/github/pyproject.toml index f882111f..8e5647ad 100644 --- a/toolkits/github/pyproject.toml +++ b/toolkits/github/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_github" version = "0.1.10" -description = "LLM tools for interacting with Github" +description = "Arcade.dev LLM tools for Github" authors = ["Arcade "] [tool.poetry.dependencies] @@ -20,7 +20,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/google/.ruff.toml b/toolkits/google/.ruff.toml index bacd9161..f1aed90f 100644 --- a/toolkits/google/.ruff.toml +++ b/toolkits/google/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/google/Makefile b/toolkits/google/Makefile index 874ba35f..0939b248 100644 --- a/toolkits/google/Makefile +++ b/toolkits/google/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/google/arcade_google/models.py b/toolkits/google/arcade_google/models.py index 152705c1..76131cf6 100644 --- a/toolkits/google/arcade_google/models.py +++ b/toolkits/google/arcade_google/models.py @@ -20,10 +20,10 @@ class DateRange(Enum): def to_datetime_range( self, - start_time: Optional[time] = None, - end_time: Optional[time] = None, - time_zone: Optional[ZoneInfo] = None, - today: Optional[date] = None, + start_time: time | None = None, + end_time: time | None = None, + time_zone: ZoneInfo | None = None, + today: date | None = None, ) -> tuple[datetime, datetime]: """ Convert a DateRange enum value to a tuple with two datetime objects representing the start @@ -427,10 +427,10 @@ class CellExtendedValue(BaseModel): Implementation of https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ExtendedValue """ - numberValue: Optional[float] = None - stringValue: Optional[str] = None - boolValue: Optional[bool] = None - formulaValue: Optional[str] = None + numberValue: float | None = None + stringValue: str | None = None + boolValue: bool | None = None + formulaValue: str | None = None errorValue: Optional["CellErrorValue"] = None @model_validator(mode="after") @@ -476,7 +476,7 @@ class CellData(BaseModel): """ userEnteredValue: CellExtendedValue - userEnteredFormat: Optional[CellFormat] = None + userEnteredFormat: CellFormat | None = None class RowData(BaseModel): @@ -517,7 +517,7 @@ class SheetProperties(BaseModel): sheetId: int title: str - gridProperties: Optional[GridProperties] = None + gridProperties: GridProperties | None = None class Sheet(BaseModel): @@ -527,7 +527,7 @@ class Sheet(BaseModel): """ properties: SheetProperties - data: Optional[list[GridData]] = None + data: list[GridData] | None = None class SpreadsheetProperties(BaseModel): @@ -612,7 +612,7 @@ class SheetDataInput(BaseModel): col_string = col_key.upper() if not col_string.isalpha(): raise TypeError(f"Column key '{col_key}' is invalid. Must be alphabetic.") - if not isinstance(cell_value, (int, float, str, bool)): + if not isinstance(cell_value, int | float | str | bool): raise TypeError( f"Cell value for {col_string}{row_int} must be an int, float, str, or bool." ) diff --git a/toolkits/google/arcade_google/tools/calendar.py b/toolkits/google/arcade_google/tools/calendar.py index bf3237c2..a5456996 100644 --- a/toolkits/google/arcade_google/tools/calendar.py +++ b/toolkits/google/arcade_google/tools/calendar.py @@ -1,6 +1,6 @@ import json from datetime import datetime, timedelta -from typing import Annotated, Any, Optional +from typing import Annotated, Any from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from arcade.sdk import ToolContext, tool @@ -373,18 +373,18 @@ async def delete_event( async def find_time_slots_when_everyone_is_free( context: ToolContext, email_addresses: Annotated[ - Optional[list[str]], + list[str] | None, "The list of email addresses from people in the same organization domain (apart from the " "currently logged in user) to search for free time slots. Defaults to None, which will " "return free time slots for the current user only.", ] = None, start_date: Annotated[ - Optional[str], + str | None, "The start date to search for time slots in the format 'YYYY-MM-DD'. Defaults to today's " "date. It will search starting from this date at the time 00:00:00.", ] = None, end_date: Annotated[ - Optional[str], + str | None, "The end date to search for time slots in the format 'YYYY-MM-DD'. Defaults to seven days " "from the start date. It will search until this date at the time 23:59:59.", ] = None, diff --git a/toolkits/google/arcade_google/tools/contacts.py b/toolkits/google/arcade_google/tools/contacts.py index 37cf478c..6ac6134e 100644 --- a/toolkits/google/arcade_google/tools/contacts.py +++ b/toolkits/google/arcade_google/tools/contacts.py @@ -1,5 +1,5 @@ import asyncio -from typing import Annotated, Optional +from typing import Annotated from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Google @@ -25,7 +25,7 @@ async def search_contacts_by_email( context: ToolContext, email: Annotated[str, "The email address to search for"], limit: Annotated[ - Optional[int], + int | None, "The maximum number of contacts to return (30 is the max allowed by Google API)", ] = DEFAULT_SEARCH_CONTACTS_LIMIT, ) -> Annotated[dict, "A dictionary containing the list of matching contacts"]: @@ -47,7 +47,7 @@ async def search_contacts_by_name( context: ToolContext, name: Annotated[str, "The full name to search for"], limit: Annotated[ - Optional[int], + int | None, "The maximum number of contacts to return (30 is the max allowed by Google API)", ] = DEFAULT_SEARCH_CONTACTS_LIMIT, ) -> Annotated[dict, "A dictionary containing the list of matching contacts"]: @@ -67,8 +67,8 @@ async def search_contacts_by_name( async def create_contact( context: ToolContext, given_name: Annotated[str, "The given name of the contact"], - family_name: Annotated[Optional[str], "The optional family name of the contact"], - email: Annotated[Optional[str], "The optional email address of the contact"], + family_name: Annotated[str | None, "The optional family name of the contact"], + email: Annotated[str | None, "The optional email address of the contact"], ) -> Annotated[dict, "A dictionary containing the details of the created contact"]: """ Create a new contact record in Google Contacts. diff --git a/toolkits/google/arcade_google/tools/drive.py b/toolkits/google/arcade_google/tools/drive.py index 7f4e1d25..cd8596d1 100644 --- a/toolkits/google/arcade_google/tools/drive.py +++ b/toolkits/google/arcade_google/tools/drive.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Google @@ -27,7 +27,7 @@ async def get_file_tree_structure( bool, "Whether to include shared drives in the file tree structure. Defaults to False." ] = False, restrict_to_shared_drive_id: Annotated[ - Optional[str], + str | None, "If provided, only include files from this shared drive in the file tree structure. " "Defaults to None, which will include files and folders from all drives.", ] = None, @@ -38,11 +38,11 @@ async def get_file_tree_structure( "account. Defaults to False.", ] = False, order_by: Annotated[ - Optional[list[OrderBy]], + list[OrderBy] | None, "Sort order. Defaults to listing the most recently modified documents first", ] = None, limit: Annotated[ - Optional[int], + int | None, "The number of files and folders to list. Defaults to None, " "which will list all files and folders.", ] = None, @@ -121,17 +121,17 @@ async def get_file_tree_structure( async def search_documents( context: ToolContext, document_contains: Annotated[ - Optional[list[str]], + list[str] | None, "Keywords or phrases that must be in the document title or body. Provide a list of " "keywords or phrases if needed.", ] = None, document_not_contains: Annotated[ - Optional[list[str]], + list[str] | None, "Keywords or phrases that must NOT be in the document title or body. Provide a list of " "keywords or phrases if needed.", ] = None, search_only_in_shared_drive_id: Annotated[ - Optional[str], + str | None, "The ID of the shared drive to restrict the search to. If provided, the search will only " "return documents from this drive. Defaults to None, which searches across all drives.", ] = None, @@ -147,12 +147,12 @@ async def search_documents( "account. Defaults to False.", ] = False, order_by: Annotated[ - Optional[list[OrderBy]], + list[OrderBy] | None, "Sort order. Defaults to listing the most recently modified documents first", ] = None, limit: Annotated[int, "The number of documents to list"] = 50, pagination_token: Annotated[ - Optional[str], "The pagination token to continue a previous request" + str | None, "The pagination token to continue a previous request" ] = None, ) -> Annotated[ dict, @@ -215,17 +215,17 @@ async def search_and_retrieve_documents( "The format of the document to return. Defaults to Markdown.", ] = DocumentFormat.MARKDOWN, document_contains: Annotated[ - Optional[list[str]], + list[str] | None, "Keywords or phrases that must be in the document title or body. Provide a list of " "keywords or phrases if needed.", ] = None, document_not_contains: Annotated[ - Optional[list[str]], + list[str] | None, "Keywords or phrases that must NOT be in the document title or body. Provide a list of " "keywords or phrases if needed.", ] = None, search_only_in_shared_drive_id: Annotated[ - Optional[str], + str | None, "The ID of the shared drive to restrict the search to. If provided, the search will only " "return documents from this drive. Defaults to None, which searches across all drives.", ] = None, @@ -241,12 +241,12 @@ async def search_and_retrieve_documents( "account. Defaults to False.", ] = False, order_by: Annotated[ - Optional[list[OrderBy]], + list[OrderBy] | None, "Sort order. Defaults to listing the most recently modified documents first", ] = None, limit: Annotated[int, "The number of documents to list"] = 50, pagination_token: Annotated[ - Optional[str], "The pagination token to continue a previous request" + str | None, "The pagination token to continue a previous request" ] = None, ) -> Annotated[ dict, diff --git a/toolkits/google/arcade_google/tools/gmail.py b/toolkits/google/arcade_google/tools/gmail.py index 87c86df7..8cf8c1a9 100644 --- a/toolkits/google/arcade_google/tools/gmail.py +++ b/toolkits/google/arcade_google/tools/gmail.py @@ -1,6 +1,6 @@ import base64 from email.mime.text import MIMEText -from typing import Annotated, Any, Optional +from typing import Annotated, Any from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Google @@ -40,8 +40,8 @@ async def send_email( subject: Annotated[str, "The subject of the email"], body: Annotated[str, "The body of the email"], recipient: Annotated[str, "The recipient of the email"], - cc: Annotated[Optional[list[str]], "CC recipients of the email"] = None, - bcc: Annotated[Optional[list[str]], "BCC recipients of the email"] = None, + cc: Annotated[list[str] | None, "CC recipients of the email"] = None, + bcc: Annotated[list[str] | None, "BCC recipients of the email"] = None, ) -> Annotated[dict, "A dictionary containing the sent email details"]: """ Send an email using the Gmail API. @@ -96,7 +96,7 @@ async def reply_to_email( "Whether to reply to every recipient (including cc) or only to the original sender. " f"Defaults to '{GMAIL_DEFAULT_REPLY_TO}'.", ] = GMAIL_DEFAULT_REPLY_TO, - bcc: Annotated[Optional[list[str]], "BCC recipients of the email"] = None, + bcc: Annotated[list[str] | None, "BCC recipients of the email"] = None, ) -> Annotated[dict, "A dictionary containing the sent email details"]: """ Send a reply to an email message. @@ -156,8 +156,8 @@ async def write_draft_email( subject: Annotated[str, "The subject of the draft email"], body: Annotated[str, "The body of the draft email"], recipient: Annotated[str, "The recipient of the draft email"], - cc: Annotated[Optional[list[str]], "CC recipients of the draft email"] = None, - bcc: Annotated[Optional[list[str]], "BCC recipients of the draft email"] = None, + cc: Annotated[list[str] | None, "CC recipients of the draft email"] = None, + bcc: Annotated[list[str] | None, "BCC recipients of the draft email"] = None, ) -> Annotated[dict, "A dictionary containing the created draft email details"]: """ Compose a new email draft using the Gmail API. @@ -193,7 +193,7 @@ async def write_draft_reply_email( "Whether to reply to every recipient (including cc) or only to the original sender. " f"Defaults to '{GMAIL_DEFAULT_REPLY_TO}'.", ] = GMAIL_DEFAULT_REPLY_TO, - bcc: Annotated[Optional[list[str]], "BCC recipients of the draft reply email"] = None, + bcc: Annotated[list[str] | None, "BCC recipients of the draft reply email"] = None, ) -> Annotated[dict, "A dictionary containing the created draft reply email details"]: """ Compose a draft reply to an email message. @@ -256,8 +256,8 @@ async def update_draft_email( subject: Annotated[str, "The subject of the draft email"], body: Annotated[str, "The body of the draft email"], recipient: Annotated[str, "The recipient of the draft email"], - cc: Annotated[Optional[list[str]], "CC recipients of the draft email"] = None, - bcc: Annotated[Optional[list[str]], "BCC recipients of the draft email"] = None, + cc: Annotated[list[str] | None, "CC recipients of the draft email"] = None, + bcc: Annotated[list[str] | None, "BCC recipients of the draft email"] = None, ) -> Annotated[dict, "A dictionary containing the updated draft email details"]: """ Update an existing email draft using the Gmail API. @@ -374,12 +374,12 @@ async def list_draft_emails( ) async def list_emails_by_header( context: ToolContext, - sender: Annotated[Optional[str], "The name or email address of the sender of the email"] = None, - recipient: Annotated[Optional[str], "The name or email address of the recipient"] = None, - subject: Annotated[Optional[str], "Words to find in the subject of the email"] = None, - body: Annotated[Optional[str], "Words to find in the body of the email"] = None, - date_range: Annotated[Optional[DateRange], "The date range of the email"] = None, - label: Annotated[Optional[str], "The label name to filter by"] = None, + sender: Annotated[str | None, "The name or email address of the sender of the email"] = None, + recipient: Annotated[str | None, "The name or email address of the recipient"] = None, + subject: Annotated[str | None, "Words to find in the subject of the email"] = None, + body: Annotated[str | None, "Words to find in the body of the email"] = None, + date_range: Annotated[DateRange | None, "The date range of the email"] = None, + label: Annotated[str | None, "The label name to filter by"] = None, max_results: Annotated[int, "The maximum number of emails to return"] = 25, ) -> Annotated[ dict, "A dictionary containing a list of email details matching the search criteria" @@ -475,16 +475,16 @@ async def list_emails( async def search_threads( context: ToolContext, page_token: Annotated[ - Optional[str], "Page token to retrieve a specific page of results in the list" + str | None, "Page token to retrieve a specific page of results in the list" ] = None, max_results: Annotated[int, "The maximum number of threads to return"] = 10, include_spam_trash: Annotated[bool, "Whether to include spam and trash in the results"] = False, - label_ids: Annotated[Optional[list[str]], "The IDs of labels to filter by"] = None, - sender: Annotated[Optional[str], "The name or email address of the sender of the email"] = None, - recipient: Annotated[Optional[str], "The name or email address of the recipient"] = None, - subject: Annotated[Optional[str], "Words to find in the subject of the email"] = None, - body: Annotated[Optional[str], "Words to find in the body of the email"] = None, - date_range: Annotated[Optional[DateRange], "The date range of the email"] = None, + label_ids: Annotated[list[str] | None, "The IDs of labels to filter by"] = None, + sender: Annotated[str | None, "The name or email address of the sender of the email"] = None, + recipient: Annotated[str | None, "The name or email address of the recipient"] = None, + subject: Annotated[str | None, "Words to find in the subject of the email"] = None, + body: Annotated[str | None, "Words to find in the body of the email"] = None, + date_range: Annotated[DateRange | None, "The date range of the email"] = None, ) -> Annotated[dict, "A dictionary containing a list of thread details"]: """Search for threads in the user's mailbox""" service = _build_gmail_service(context) @@ -535,7 +535,7 @@ async def search_threads( async def list_threads( context: ToolContext, page_token: Annotated[ - Optional[str], "Page token to retrieve a specific page of results in the list" + str | None, "Page token to retrieve a specific page of results in the list" ] = None, max_results: Annotated[int, "The maximum number of threads to return"] = 10, include_spam_trash: Annotated[bool, "Whether to include spam and trash in the results"] = False, diff --git a/toolkits/google/arcade_google/tools/sheets.py b/toolkits/google/arcade_google/tools/sheets.py index 555bcb59..1248fab6 100644 --- a/toolkits/google/arcade_google/tools/sheets.py +++ b/toolkits/google/arcade_google/tools/sheets.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Google @@ -27,7 +27,7 @@ def create_spreadsheet( context: ToolContext, title: Annotated[str, "The title of the new spreadsheet"] = "Untitled spreadsheet", data: Annotated[ - Optional[str], + str | None, "The data to write to the spreadsheet. A JSON string " "(property names enclosed in double quotes) representing a dictionary that " "maps row numbers to dictionaries that map column letters to cell values. " diff --git a/toolkits/google/arcade_google/utils.py b/toolkits/google/arcade_google/utils.py index 75b6d520..07b13a73 100644 --- a/toolkits/google/arcade_google/utils.py +++ b/toolkits/google/arcade_google/utils.py @@ -5,7 +5,7 @@ from datetime import date, datetime, time, timedelta, timezone from email.message import EmailMessage from email.mime.text import MIMEText from enum import Enum -from typing import Any, Optional, Union, cast +from typing import Any, cast from zoneinfo import ZoneInfo from arcade.sdk import ToolContext @@ -114,15 +114,15 @@ def build_email_message( recipient: str, subject: str, body: str, - cc: Optional[list[str]] = None, - bcc: Optional[list[str]] = None, - replying_to: Optional[dict[str, Any]] = None, + cc: list[str] | None = None, + bcc: list[str] | None = None, + replying_to: dict[str, Any] | None = None, action: GmailAction = GmailAction.SEND, ) -> dict[str, Any]: if replying_to: body = build_reply_body(body, replying_to) - message: Union[EmailMessage, MIMEText] + message: EmailMessage | MIMEText if action == GmailAction.SEND: message = EmailMessage() @@ -183,10 +183,10 @@ def parse_plain_text_email(email_data: dict[str, Any]) -> dict[str, Any]: Only returns the plain text body. Args: - email_data (Dict[str, Any]): Raw email data from Gmail API. + email_data (dict[str, Any]): Raw email data from Gmail API. Returns: - Optional[Dict[str, str]]: Parsed email details or None if parsing fails. + dict[str, str]: Parsed email details """ payload = email_data.get("payload", {}) headers = {d["name"].lower(): d["value"] for d in payload.get("headers", [])} @@ -223,7 +223,7 @@ def parse_multipart_email(email_data: dict[str, Any]) -> dict[str, Any]: email_data (Dict[str, Any]): Raw email data from Gmail API. Returns: - Optional[Dict[str, Any]]: Parsed email details or None if parsing fails. + dict[str, Any]: Parsed email details """ payload = email_data.get("payload", {}) @@ -263,7 +263,7 @@ def parse_draft_email(draft_email_data: dict[str, Any]) -> dict[str, str]: draft_email_data (Dict[str, Any]): Raw draft email data from Gmail API. Returns: - Optional[Dict[str, str]]: Parsed draft email details or None if parsing fails. + dict[str, str]: Parsed draft email details """ message = draft_email_data.get("message", {}) payload = message.get("payload", {}) @@ -342,7 +342,7 @@ def _build_gmail_service(context: ToolContext) -> Any: return build("gmail", "v1", credentials=credentials) -def _extract_plain_body(parts: list) -> Optional[str]: +def _extract_plain_body(parts: list) -> str | None: """ Recursively extract the email body from parts, handling both plain text and HTML. @@ -350,7 +350,7 @@ def _extract_plain_body(parts: list) -> Optional[str]: parts (List[Dict[str, Any]]): List of email parts. Returns: - Optional[str]: Decoded and cleaned email body or None if not found. + str | None: Decoded and cleaned email body or None if not found. """ for part in parts: mime_type = part.get("mimeType") @@ -367,7 +367,7 @@ def _extract_plain_body(parts: list) -> Optional[str]: return _extract_html_body(parts) -def _extract_html_body(parts: list) -> Optional[str]: +def _extract_html_body(parts: list) -> str | None: """ Recursively extract the email body from parts, handling only HTML. @@ -375,7 +375,7 @@ def _extract_html_body(parts: list) -> Optional[str]: parts (List[Dict[str, Any]]): List of email parts. Returns: - Optional[str]: Decoded and cleaned email body or None if not found. + str | None: Decoded and cleaned email body or None if not found. """ for part in parts: mime_type = part.get("mimeType") @@ -393,7 +393,7 @@ def _extract_html_body(parts: list) -> Optional[str]: return None -def _get_email_images(payload: dict[str, Any]) -> Optional[list[str]]: +def _get_email_images(payload: dict[str, Any]) -> list[str] | None: """ Extract the email images from an email payload. @@ -401,7 +401,7 @@ def _get_email_images(payload: dict[str, Any]) -> Optional[list[str]]: payload (Dict[str, Any]): Email payload data. Returns: - Optional[List[str]]: List of decoded image contents or None if none found. + list[str] | None: List of decoded image contents or None if none found. """ images = [] for part in payload.get("parts", []): @@ -423,7 +423,7 @@ def _get_email_images(payload: dict[str, Any]) -> Optional[list[str]]: return None -def _get_email_plain_text_body(payload: dict[str, Any]) -> Optional[str]: +def _get_email_plain_text_body(payload: dict[str, Any]) -> str | None: """ Extract email body from payload, handling 'multipart/alternative' parts. @@ -431,7 +431,7 @@ def _get_email_plain_text_body(payload: dict[str, Any]) -> Optional[str]: payload (Dict[str, Any]): Email payload data. Returns: - Optional[str]: Decoded email body or None if not found. + str | None: Decoded email body or None if not found. """ # Direct body extraction if "body" in payload and payload["body"].get("data"): @@ -441,7 +441,7 @@ def _get_email_plain_text_body(payload: dict[str, Any]) -> Optional[str]: return _clean_email_body(_extract_plain_body(payload.get("parts", []))) -def _get_email_html_body(payload: dict[str, Any]) -> Optional[str]: +def _get_email_html_body(payload: dict[str, Any]) -> str | None: """ Extract email html body from payload, handling 'multipart/alternative' parts. @@ -449,7 +449,7 @@ def _get_email_html_body(payload: dict[str, Any]) -> Optional[str]: payload (Dict[str, Any]): Email payload data. Returns: - Optional[str]: Decoded email body or None if not found. + str | None: Decoded email body or None if not found. """ # Direct body extraction if "body" in payload and payload["body"].get("data"): @@ -459,7 +459,7 @@ def _get_email_html_body(payload: dict[str, Any]) -> Optional[str]: return _extract_html_body(payload.get("parts", [])) -def _clean_email_body(body: Optional[str]) -> str: +def _clean_email_body(body: str | None) -> str: """ Remove HTML tags and clean up email body text while preserving most content. @@ -608,7 +608,7 @@ def remove_none_values(params: dict) -> dict: # Drive utils -def build_drive_service(auth_token: Optional[str]) -> Resource: # type: ignore[no-any-unimported] +def build_drive_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported] """ Build a Drive service object. """ @@ -618,8 +618,8 @@ def build_drive_service(auth_token: Optional[str]) -> Resource: # type: ignore[ def build_files_list_query( mime_type: str, - document_contains: Optional[list[str]] = None, - document_not_contains: Optional[list[str]] = None, + document_contains: list[str] | None = None, + document_not_contains: list[str] | None = None, ) -> str: query = [f"(mimeType = '{mime_type}' and trashed = false)"] @@ -655,12 +655,12 @@ def build_files_list_params( mime_type: str, page_size: int, order_by: list[OrderBy], - pagination_token: Optional[str], + pagination_token: str | None, include_shared_drives: bool, - search_only_in_shared_drive_id: Optional[str], + search_only_in_shared_drive_id: str | None, include_organization_domain_documents: bool, - document_contains: Optional[list[str]] = None, - document_not_contains: Optional[list[str]] = None, + document_contains: list[str] | None = None, + document_not_contains: list[str] | None = None, ) -> dict[str, Any]: query = build_files_list_query( mime_type=mime_type, @@ -696,11 +696,11 @@ def build_files_list_params( def build_file_tree_request_params( - order_by: Optional[list[OrderBy]], - page_token: Optional[str], - limit: Optional[int], + order_by: list[OrderBy] | None, + page_token: str | None, + limit: int | None, include_shared_drives: bool, - restrict_to_shared_drive_id: Optional[str], + restrict_to_shared_drive_id: str | None, include_organization_domain_documents: bool, ) -> dict[str, Any]: if order_by is None: @@ -787,7 +787,7 @@ def build_file_tree(files: dict[str, Any]) -> dict[str, Any]: # Docs utils -def build_docs_service(auth_token: Optional[str]) -> Resource: # type: ignore[no-any-unimported] +def build_docs_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported] """ Build a Drive service object. """ @@ -989,7 +989,7 @@ def get_now(tz: ZoneInfo | None = None) -> datetime: # Contacts utils -def build_people_service(auth_token: Optional[str]) -> Resource: # type: ignore[no-any-unimported] +def build_people_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported] """ Build a People service object. """ @@ -997,7 +997,7 @@ def build_people_service(auth_token: Optional[str]) -> Resource: # type: ignore return build("people", "v1", credentials=Credentials(auth_token)) -def search_contacts(service: Any, query: str, limit: Optional[int]) -> list[dict[str, Any]]: +def search_contacts(service: Any, query: str, limit: int | None) -> list[dict[str, Any]]: """ Search the user's contacts in Google Contacts. """ @@ -1029,7 +1029,7 @@ def search_contacts(service: Any, query: str, limit: Optional[int]) -> list[dict # ---------------------------------------------------------------- -def build_sheets_service(auth_token: Optional[str]) -> Resource: # type: ignore[no-any-unimported] +def build_sheets_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported] """ Build a Sheets service object. """ diff --git a/toolkits/google/pyproject.toml b/toolkits/google/pyproject.toml index 683f12fd..0eeb5b57 100644 --- a/toolkits/google/pyproject.toml +++ b/toolkits/google/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_google" version = "1.1.0" -description = "Arcade tools for the entire google suite" +description = "Arcade.dev LLM tools for Google Workspace" authors = ["Arcade "] [tool.poetry.dependencies] @@ -26,7 +26,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/google/tests/test_sheets_utils.py b/toolkits/google/tests/test_sheets_utils.py index 420f0554..ea7c4a58 100644 --- a/toolkits/google/tests/test_sheets_utils.py +++ b/toolkits/google/tests/test_sheets_utils.py @@ -236,7 +236,7 @@ def test_create_row_data(): row_data = create_row_data(row_data, min_col_index, max_col_index) assert len(row_data.values) == len(expected_row_data.values) - for cell, expected in zip(row_data.values, expected_row_data.values): + for cell, expected in zip(row_data.values, expected_row_data.values, strict=False): assert cell.userEnteredValue == expected.userEnteredValue @@ -268,7 +268,7 @@ def test_create_sheet_data(): create_cell_data("row2B"), create_cell_data(200), ] - for cell, expected in zip(row2_cells, expected_row2): + for cell, expected in zip(row2_cells, expected_row2, strict=False): assert cell.userEnteredValue == expected.userEnteredValue row3_cells = group1.rowData[1].values @@ -277,7 +277,7 @@ def test_create_sheet_data(): create_cell_data("row3B"), CellData(userEnteredValue=CellExtendedValue(stringValue="")), ] - for cell, expected in zip(row3_cells, expected_row3): + for cell, expected in zip(row3_cells, expected_row3, strict=False): assert cell.userEnteredValue == expected.userEnteredValue group2 = grid_data_list[1] @@ -291,7 +291,7 @@ def test_create_sheet_data(): CellData(userEnteredValue=CellExtendedValue(stringValue="")), create_cell_data("row5C"), ] - for cell, expected in zip(row5_cells, expected_row5): + for cell, expected in zip(row5_cells, expected_row5, strict=False): assert cell.userEnteredValue == expected.userEnteredValue diff --git a/toolkits/linkedin/.ruff.toml b/toolkits/linkedin/.ruff.toml index bacd9161..f1aed90f 100644 --- a/toolkits/linkedin/.ruff.toml +++ b/toolkits/linkedin/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/linkedin/Makefile b/toolkits/linkedin/Makefile index 54e9af14..82c17735 100644 --- a/toolkits/linkedin/Makefile +++ b/toolkits/linkedin/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/linkedin/pyproject.toml b/toolkits/linkedin/pyproject.toml index 89aa3012..28344a62 100644 --- a/toolkits/linkedin/pyproject.toml +++ b/toolkits/linkedin/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_linkedin" version = "0.1.10" -description = "Arcade tools for LinkedIn" +description = "Arcade.dev LLM tools for LinkedIn" authors = ["Arcade "] [tool.poetry.dependencies] @@ -20,7 +20,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/math/.ruff.toml b/toolkits/math/.ruff.toml index bb64590d..19364180 100644 --- a/toolkits/math/.ruff.toml +++ b/toolkits/math/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/math/Makefile b/toolkits/math/Makefile index 27952192..55dee607 100644 --- a/toolkits/math/Makefile +++ b/toolkits/math/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/math/arcade_math/tools/random.py b/toolkits/math/arcade_math/tools/random.py index 2df19ce3..c6e8be78 100644 --- a/toolkits/math/arcade_math/tools/random.py +++ b/toolkits/math/arcade_math/tools/random.py @@ -1,5 +1,5 @@ import random -from typing import Annotated, Optional +from typing import Annotated from arcade.sdk import tool @@ -9,7 +9,7 @@ def generate_random_int( min_value: Annotated[str, "The minimum value of the random integer as a string"], max_value: Annotated[str, "The maximum value of the random integer as a string"], seed: Annotated[ - Optional[str], + str | None, "The seed for the random number generator as a string." " If None, the current system time is used.", ] = None, @@ -26,7 +26,7 @@ def generate_random_float( min_value: Annotated[str, "The minimum value of the random float as a string"], max_value: Annotated[str, "The maximum value of the random float as a string"], seed: Annotated[ - Optional[str], + str | None, "The seed for the random number generator as a string." " If None, the current system time is used.", ] = None, diff --git a/toolkits/math/pyproject.toml b/toolkits/math/pyproject.toml index 88deaf50..387f3919 100644 --- a/toolkits/math/pyproject.toml +++ b/toolkits/math/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_math" version = "1.0.1" -description = "Math toolkit for Arcade" +description = "Arcade.dev LLM tools for doing math" authors = ["Arcade "] [tool.poetry.dependencies] @@ -19,7 +19,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/notion/.ruff.toml b/toolkits/notion/.ruff.toml index 36a7a4ed..9519fe6c 100644 --- a/toolkits/notion/.ruff.toml +++ b/toolkits/notion/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/notion/Makefile b/toolkits/notion/Makefile index df32e276..73d9331d 100644 --- a/toolkits/notion/Makefile +++ b/toolkits/notion/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/notion/arcade_notion_toolkit/block_to_markdown_converter.py b/toolkits/notion/arcade_notion_toolkit/block_to_markdown_converter.py index e35a50e5..c566c727 100644 --- a/toolkits/notion/arcade_notion_toolkit/block_to_markdown_converter.py +++ b/toolkits/notion/arcade_notion_toolkit/block_to_markdown_converter.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Optional +from typing import Any from arcade.sdk import ToolContext @@ -100,14 +100,14 @@ class BlockToMarkdownConverter: return md @staticmethod - def apply_formatting(text: str, annotations: dict[str, Any], link: Optional[str] = None) -> str: + def apply_formatting(text: str, annotations: dict[str, Any], link: str | None = None) -> str: """Apply formatting to a text string based on the annotations. Used when converting rich text to markdown Args: text (str): The text to format. annotations (dict[str, Any]): The annotations to apply to the text. - link (Optional[str]): An optional link for a hyperlink. + link (str | None): An optional link for a hyperlink. Returns: str: The formatted text. diff --git a/toolkits/notion/arcade_notion_toolkit/tools/pages.py b/toolkits/notion/arcade_notion_toolkit/tools/pages.py index ede7d1cc..d98adb87 100644 --- a/toolkits/notion/arcade_notion_toolkit/tools/pages.py +++ b/toolkits/notion/arcade_notion_toolkit/tools/pages.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any import httpx from arcade.sdk import ToolContext, tool @@ -95,7 +95,7 @@ async def create_page( "Title of an existing page/database within which the new page will be created. ", ], title: Annotated[str, "Title of the new page"], - content: Annotated[Optional[str], "The content of the new page"] = None, + content: Annotated[str | None, "The content of the new page"] = None, ) -> Annotated[str, "The ID of the new page"]: """Create a new Notion page by the title of the new page's parent.""" # Notion API does not support creating a page at the root of the workspace... sigh diff --git a/toolkits/notion/arcade_notion_toolkit/tools/search.py b/toolkits/notion/arcade_notion_toolkit/tools/search.py index a3932e52..a5165cf5 100644 --- a/toolkits/notion/arcade_notion_toolkit/tools/search.py +++ b/toolkits/notion/arcade_notion_toolkit/tools/search.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any import httpx from arcade.sdk import ToolContext, tool @@ -19,12 +19,12 @@ from arcade_notion_toolkit.utils import ( async def search_by_title( context: ToolContext, query: Annotated[ - Optional[str], + str | None, "A substring to search for within page and database titles. " "If not provided (default), all pages and/or databases are returned.", ] = None, select: Annotated[ - Optional[ObjectType], + ObjectType | None, "Limit the results to either only pages or only databases. Defaults to both.", ] = None, order_by: Annotated[ @@ -87,11 +87,11 @@ async def search_by_title( async def get_object_metadata( context: ToolContext, object_title: Annotated[ - Optional[str], "Title of the page or database whose metadata to get" + str | None, "Title of the page or database whose metadata to get" ] = None, - object_id: Annotated[Optional[str], "ID of the page or database whose metadata to get"] = None, + object_id: Annotated[str | None, "ID of the page or database whose metadata to get"] = None, object_type: Annotated[ - Optional[ObjectType], + ObjectType | None, "The type of object to match title to. Only used if `object_title` is provided. " "Defaults to both", ] = None, diff --git a/toolkits/notion/arcade_notion_toolkit/utils.py b/toolkits/notion/arcade_notion_toolkit/utils.py index 7ad9509a..da7eb007 100644 --- a/toolkits/notion/arcade_notion_toolkit/utils.py +++ b/toolkits/notion/arcade_notion_toolkit/utils.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any import httpx from arcade.sdk import ToolContext @@ -118,8 +118,8 @@ async def get_next_page( client: httpx.AsyncClient, url: str, headers: dict, - params: Optional[dict] = None, - cursor: Optional[str] = None, + params: dict | None = None, + cursor: str | None = None, ) -> tuple[dict, bool, str]: """ Retrieves the next page of results from a Notion API endpoint. @@ -129,8 +129,8 @@ async def get_next_page( client (httpx.AsyncClient): The HTTP client to use for the request. url (str): The URL of the endpoint to request. headers (dict): The headers to use for the request. - params (Optional[dict]): The parameters to use for the request. - cursor (Optional[str]): The cursor to use for the request. + params (dict | None): The parameters to use for the request. + cursor (str | None): The cursor to use for the request. Returns: tuple[dict, bool, str]: A tuple containing the results, a boolean indicating if there is a diff --git a/toolkits/notion/pyproject.toml b/toolkits/notion/pyproject.toml index 4238ce43..e51715d9 100644 --- a/toolkits/notion/pyproject.toml +++ b/toolkits/notion/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_notion_toolkit" version = "0.1.1" -description = "LLM tools for essential Notion interactions such as creating, updating, retrieving, and searching pages." +description = "Arcade.dev LLM tools for Notion" authors = ["ArcadeAI "] [tool.poetry.dependencies] @@ -17,7 +17,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/reddit/.ruff.toml b/toolkits/reddit/.ruff.toml index 36a7a4ed..9519fe6c 100644 --- a/toolkits/reddit/.ruff.toml +++ b/toolkits/reddit/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/reddit/Makefile b/toolkits/reddit/Makefile index d660dac6..a94486d3 100644 --- a/toolkits/reddit/Makefile +++ b/toolkits/reddit/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/reddit/arcade_reddit/tools/read.py b/toolkits/reddit/arcade_reddit/tools/read.py index def0c055..d7b4e529 100644 --- a/toolkits/reddit/arcade_reddit/tools/read.py +++ b/toolkits/reddit/arcade_reddit/tools/read.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Reddit @@ -33,7 +33,7 @@ async def get_posts_in_subreddit( ), ] = SubredditListingType.HOT, limit: Annotated[int, "The maximum number of posts to fetch. Default is 10, max is 100."] = 10, - cursor: Annotated[Optional[str], "The pagination token from a previous call"] = None, + cursor: Annotated[str | None, "The pagination token from a previous call"] = None, time_range: Annotated[ RedditTimeFilter, "The time range for filtering posts. Must be provided if the listing type is " diff --git a/toolkits/reddit/arcade_reddit/tools/submit.py b/toolkits/reddit/arcade_reddit/tools/submit.py index 258f0076..ccebd3a3 100644 --- a/toolkits/reddit/arcade_reddit/tools/submit.py +++ b/toolkits/reddit/arcade_reddit/tools/submit.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Reddit @@ -19,20 +19,20 @@ async def submit_text_post( subreddit: Annotated[str, "The name of the subreddit to which the post will be submitted"], title: Annotated[str, "The title of the submission"], body: Annotated[ - Optional[str], + str | None, "The body of the post in markdown format. Should never be the same as the title", ] = None, nsfw: Annotated[ - Optional[bool], + bool | None, "Indicates if the submission has content that is 'Not Safe For Work' (NSFW). " "Default is False", ] = False, spoiler: Annotated[ - Optional[bool], + bool | None, "Indicates if the post is marked as a spoiler. Default is False", ] = False, send_replies: Annotated[ - Optional[bool], "If true, sends replies to the user's inbox. Default is True" + bool | None, "If true, sends replies to the user's inbox. Default is True" ] = True, ) -> Annotated[dict, "Response from Reddit after submission"]: """Submit a text-based post to a subreddit""" diff --git a/toolkits/reddit/pyproject.toml b/toolkits/reddit/pyproject.toml index d81e37bc..49d0a653 100644 --- a/toolkits/reddit/pyproject.toml +++ b/toolkits/reddit/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_reddit" version = "0.0.1" -description = "LLM tools for interacting with Reddit" +description = "Arcade.dev LLM tools Reddit" authors = ["Arcade "] [tool.poetry.dependencies] @@ -17,7 +17,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/search/.ruff.toml b/toolkits/search/.ruff.toml index bb64590d..19364180 100644 --- a/toolkits/search/.ruff.toml +++ b/toolkits/search/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/search/Makefile b/toolkits/search/Makefile index 75a9dd7f..024c8962 100644 --- a/toolkits/search/Makefile +++ b/toolkits/search/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/search/arcade_search/enums.py b/toolkits/search/arcade_search/enums.py index 876b8de4..b1e31d8d 100644 --- a/toolkits/search/arcade_search/enums.py +++ b/toolkits/search/arcade_search/enums.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Optional # ------------------------------------------------------------------------------------------------ @@ -138,7 +137,7 @@ class WalmartSortBy(Enum): RATING_HIGH = "highest_rating_first" NEW_ARRIVALS = "new_arrivals_first" - def to_api_value(self: "WalmartSortBy") -> Optional[str]: + def to_api_value(self: "WalmartSortBy") -> str | None: _map = { str(self.RELEVANCE): None, str(self.PRICE_LOW_TO_HIGH): "price_low", diff --git a/toolkits/search/arcade_search/exceptions.py b/toolkits/search/arcade_search/exceptions.py index 9e3c1b7c..0a186753 100644 --- a/toolkits/search/arcade_search/exceptions.py +++ b/toolkits/search/arcade_search/exceptions.py @@ -1,5 +1,4 @@ import json -from typing import Optional from arcade.sdk.errors import RetryableToolError @@ -11,7 +10,7 @@ class GoogleRetryableError(RetryableToolError): class CountryNotFoundError(GoogleRetryableError): - def __init__(self, country: Optional[str]) -> None: + def __init__(self, country: str | None) -> None: valid_countries = json.dumps(COUNTRY_CODES, default=str) message = f"Country not found: '{country}'." additional_message = f"Valid countries are: {valid_countries}" @@ -19,7 +18,7 @@ class CountryNotFoundError(GoogleRetryableError): class LanguageNotFoundError(GoogleRetryableError): - def __init__(self, language: Optional[str]) -> None: + def __init__(self, language: str | None) -> None: valid_languages = json.dumps(LANGUAGE_CODES, default=str) message = f"Language not found: '{language}'." additional_message = f"Valid languages are: {valid_languages}" diff --git a/toolkits/search/arcade_search/tools/google_flights.py b/toolkits/search/arcade_search/tools/google_flights.py index e989746a..bdf1cd09 100644 --- a/toolkits/search/arcade_search/tools/google_flights.py +++ b/toolkits/search/arcade_search/tools/google_flights.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any from arcade.sdk import ToolContext, tool @@ -14,16 +14,16 @@ async def search_roundtrip_flights( ], arrival_airport_code: Annotated[str, "The arrival airport code. An uppercase 3-letter code"], outbound_date: Annotated[str, "Flight outbound date in YYYY-MM-DD format"], - return_date: Annotated[Optional[str], "Flight return date in YYYY-MM-DD format"], + return_date: Annotated[str | None, "Flight return date in YYYY-MM-DD format"], currency_code: Annotated[ - Optional[str], "Currency of the returned prices. Defaults to 'USD'" + str | None, "Currency of the returned prices. Defaults to 'USD'" ] = "USD", travel_class: Annotated[ GoogleFlightsTravelClass, "Travel class of the flight. Defaults to 'ECONOMY'", ] = GoogleFlightsTravelClass.ECONOMY, - num_adults: Annotated[Optional[int], "Number of adult passengers. Defaults to 1"] = 1, - num_children: Annotated[Optional[int], "Number of child passengers. Defaults to 0"] = 0, + num_adults: Annotated[int | None, "Number of adult passengers. Defaults to 1"] = 1, + num_children: Annotated[int | None, "Number of child passengers. Defaults to 0"] = 0, max_stops: Annotated[ GoogleFlightsMaxStops, "Maximum number of stops (layovers) for the flight. Defaults to any number of stops", @@ -68,14 +68,14 @@ async def search_one_way_flights( arrival_airport_code: Annotated[str, "The arrival airport code. An uppercase 3-letter code"], outbound_date: Annotated[str, "Flight departure date in YYYY-MM-DD format"], currency_code: Annotated[ - Optional[str], "Currency of the returned prices. Defaults to 'USD'" + str | None, "Currency of the returned prices. Defaults to 'USD'" ] = "USD", travel_class: Annotated[ GoogleFlightsTravelClass, "Travel class of the flight. Defaults to 'ECONOMY'", ] = GoogleFlightsTravelClass.ECONOMY, - num_adults: Annotated[Optional[int], "Number of adult passengers. Defaults to 1"] = 1, - num_children: Annotated[Optional[int], "Number of child passengers. Defaults to 0"] = 0, + num_adults: Annotated[int | None, "Number of adult passengers. Defaults to 1"] = 1, + num_children: Annotated[int | None, "Number of child passengers. Defaults to 0"] = 0, max_stops: Annotated[ GoogleFlightsMaxStops, "Maximum number of stops (layovers) for the flight. Defaults to any number of stops", diff --git a/toolkits/search/arcade_search/tools/google_hotels.py b/toolkits/search/arcade_search/tools/google_hotels.py index f943c8af..21f7e3bf 100644 --- a/toolkits/search/arcade_search/tools/google_hotels.py +++ b/toolkits/search/arcade_search/tools/google_hotels.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any from arcade.sdk import ToolContext, tool @@ -13,18 +13,18 @@ async def search_hotels( check_in_date: Annotated[str, "Check-in date in YYYY-MM-DD format"], check_out_date: Annotated[str, "Check-out date in YYYY-MM-DD format"], query: Annotated[ - Optional[str], "Anything that would be used in a regular Google Hotels search" + str | None, "Anything that would be used in a regular Google Hotels search" ] = None, - currency: Annotated[Optional[str], "Currency code for prices. Defaults to 'USD'"] = "USD", - min_price: Annotated[Optional[int], "Minimum price per night. Defaults to no minimum"] = None, - max_price: Annotated[Optional[int], "Maximum price per night. Defaults to no maximum"] = None, - num_adults: Annotated[Optional[int], "Number of adults per room. Defaults to 2"] = 2, - num_children: Annotated[Optional[int], "Number of children per room. Defaults to 0"] = 0, + currency: Annotated[str | None, "Currency code for prices. Defaults to 'USD'"] = "USD", + min_price: Annotated[int | None, "Minimum price per night. Defaults to no minimum"] = None, + max_price: Annotated[int | None, "Maximum price per night. Defaults to no maximum"] = None, + num_adults: Annotated[int | None, "Number of adults per room. Defaults to 2"] = 2, + num_children: Annotated[int | None, "Number of children per room. Defaults to 0"] = 0, sort_by: Annotated[ GoogleHotelsSortBy, "The sorting order of the results. Defaults to RELEVANCE" ] = GoogleHotelsSortBy.RELEVANCE, num_results: Annotated[ - Optional[int], "Maximum number of results to return. Defaults to 5. Max 20" + int | None, "Maximum number of results to return. Defaults to 5. Max 20" ] = 5, ) -> Annotated[dict[str, Any], "Hotel search results from the Google Hotels API"]: """Retrieve hotel search results using the Google Hotels API.""" diff --git a/toolkits/search/arcade_search/tools/google_jobs.py b/toolkits/search/arcade_search/tools/google_jobs.py index dcd35452..2eaa677f 100644 --- a/toolkits/search/arcade_search/tools/google_jobs.py +++ b/toolkits/search/arcade_search/tools/google_jobs.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from arcade.sdk import ToolContext, tool @@ -18,7 +18,7 @@ async def search_jobs( "or 'data analyst at Apple'.", ], location: Annotated[ - Optional[str], + str | None, "Location to search for jobs. E.g. 'United States' or 'New York, NY'. Defaults to None.", ] = None, language: Annotated[ @@ -31,7 +31,7 @@ async def search_jobs( "Maximum number of results to retrieve. Defaults to 10 (max supported by the API).", ] = 10, next_page_token: Annotated[ - Optional[str], + str | None, "Next page token to paginate results. Defaults to None (start from the first page).", ] = None, ) -> Annotated[dict, "Google Jobs results"]: diff --git a/toolkits/search/arcade_search/tools/google_maps.py b/toolkits/search/arcade_search/tools/google_maps.py index f11f05f9..cf93cd3a 100644 --- a/toolkits/search/arcade_search/tools/google_maps.py +++ b/toolkits/search/arcade_search/tools/google_maps.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from arcade.sdk import ToolContext, tool @@ -27,7 +27,7 @@ async def get_directions_between_addresses( f"Defaults to '{DEFAULT_GOOGLE_MAPS_LANGUAGE}'.", ] = DEFAULT_GOOGLE_MAPS_LANGUAGE, country: Annotated[ - Optional[str], + str | None, "2-character country code to use in the Google Maps search. " f"Defaults to '{DEFAULT_GOOGLE_MAPS_COUNTRY}'.", ] = DEFAULT_GOOGLE_MAPS_COUNTRY, @@ -69,7 +69,7 @@ async def get_directions_between_coordinates( f"Defaults to '{DEFAULT_GOOGLE_MAPS_LANGUAGE}'.", ] = DEFAULT_GOOGLE_MAPS_LANGUAGE, country: Annotated[ - Optional[str], + str | None, f"2-letter country code to use in the Google Maps search. Defaults to " f"'{DEFAULT_GOOGLE_MAPS_COUNTRY}'.", ] = DEFAULT_GOOGLE_MAPS_COUNTRY, diff --git a/toolkits/search/arcade_search/tools/google_news.py b/toolkits/search/arcade_search/tools/google_news.py index ca58b928..58815f69 100644 --- a/toolkits/search/arcade_search/tools/google_news.py +++ b/toolkits/search/arcade_search/tools/google_news.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any from arcade.sdk import ToolContext, tool from arcade.sdk.errors import ToolExecutionError @@ -17,7 +17,7 @@ async def search_news_stories( "Keywords to search for news articles. E.g. 'Apple launches new iPhone'.", ], country_code: Annotated[ - Optional[str], + str | None, "2-character country code to search for news articles. E.g. 'us' (United States). " f"Defaults to '{DEFAULT_GOOGLE_NEWS_COUNTRY}'.", ] = None, @@ -27,7 +27,7 @@ async def search_news_stories( f"Defaults to '{DEFAULT_GOOGLE_NEWS_LANGUAGE}'.", ] = DEFAULT_GOOGLE_NEWS_LANGUAGE, limit: Annotated[ - Optional[int], + int | None, "Maximum number of news articles to return. Defaults to None " "(returns all results found by the API).", ] = None, diff --git a/toolkits/search/arcade_search/tools/google_shopping.py b/toolkits/search/arcade_search/tools/google_shopping.py index 1195aca2..4d50a1f1 100644 --- a/toolkits/search/arcade_search/tools/google_shopping.py +++ b/toolkits/search/arcade_search/tools/google_shopping.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any from arcade.sdk import ToolContext, tool from arcade.sdk.errors import ToolExecutionError @@ -25,12 +25,12 @@ async def search_shopping_products( "Keywords to search for products in Google Shopping. E.g. 'Apple iPhone'.", ], country_code: Annotated[ - Optional[str], + str | None, "2-character country code to search for products in Google Shopping. " f"E.g. 'us' (United States). Defaults to '{DEFAULT_GOOGLE_SHOPPING_COUNTRY or 'us'}'.", ] = DEFAULT_GOOGLE_SHOPPING_COUNTRY, language_code: Annotated[ - Optional[str], + str | None, "2-character language code to search for products on Google Shopping. E.g. 'en' (English). " f"Defaults to '{DEFAULT_GOOGLE_SHOPPING_LANGUAGE or 'en'}'.", ] = DEFAULT_GOOGLE_SHOPPING_LANGUAGE, diff --git a/toolkits/search/arcade_search/tools/walmart.py b/toolkits/search/arcade_search/tools/walmart.py index 3c0234f8..68a7a06b 100644 --- a/toolkits/search/arcade_search/tools/walmart.py +++ b/toolkits/search/arcade_search/tools/walmart.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any from arcade.sdk import ToolContext from arcade.sdk.errors import ToolExecutionError @@ -24,11 +24,11 @@ async def search_walmart_products( f"Defaults to '{WalmartSortBy.RELEVANCE.value}'.", ] = WalmartSortBy.RELEVANCE, min_price: Annotated[ - Optional[float], + float | None, "Minimum price to filter the results by. E.g. 100.00", ] = None, max_price: Annotated[ - Optional[float], + float | None, "Maximum price to filter the results by. E.g. 100.00", ] = None, next_day_delivery: Annotated[ diff --git a/toolkits/search/arcade_search/tools/youtube.py b/toolkits/search/arcade_search/tools/youtube.py index fdae217f..4b75c362 100644 --- a/toolkits/search/arcade_search/tools/youtube.py +++ b/toolkits/search/arcade_search/tools/youtube.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional, cast +from typing import Annotated, Any, cast from arcade.sdk import ToolContext, tool from arcade.sdk.errors import ToolExecutionError @@ -24,17 +24,17 @@ async def search_youtube_videos( "The keywords to search for. E.g. 'Python tutorial'.", ], language_code: Annotated[ - Optional[str], + str | None, "2-character language code to search for. E.g. 'en' for English. " f"Defaults to '{default_language_code(DEFAULT_YOUTUBE_SEARCH_LANGUAGE)}'.", ] = None, country_code: Annotated[ - Optional[str], + str | None, "2-character country code to search for. E.g. 'us' for United States. " f"Defaults to '{default_country_code(DEFAULT_YOUTUBE_SEARCH_COUNTRY)}'.", ] = None, next_page_token: Annotated[ - Optional[str], + str | None, "The next page token to use for pagination. " "Defaults to `None` (start from the first page).", ] = None, @@ -70,12 +70,12 @@ async def get_youtube_video_details( "The ID of the YouTube video to get details about. E.g. 'dQw4w9WgXcQ'.", ], language_code: Annotated[ - Optional[str], + str | None, "2-character language code to search for. E.g. 'en' for English. " f"Defaults to '{default_language_code(DEFAULT_YOUTUBE_SEARCH_LANGUAGE)}'.", ] = None, country_code: Annotated[ - Optional[str], + str | None, "2-character country code to search for. E.g. 'us' for United States. " f"Defaults to '{default_country_code(DEFAULT_YOUTUBE_SEARCH_COUNTRY)}'.", ] = None, diff --git a/toolkits/search/arcade_search/utils.py b/toolkits/search/arcade_search/utils.py index fe602ac8..ca62c26b 100644 --- a/toolkits/search/arcade_search/utils.py +++ b/toolkits/search/arcade_search/utils.py @@ -1,7 +1,7 @@ import contextlib import re from datetime import datetime -from typing import Any, Optional, cast +from typing import Any, cast from urllib.parse import parse_qs, urlparse from zoneinfo import ZoneInfo @@ -71,7 +71,7 @@ def call_serpapi(context: ToolContext, params: dict) -> dict: # ------------------------------------------------------------------------------------------------ # Google general utils # ------------------------------------------------------------------------------------------------ -def default_language_code(default_service_language_code: Optional[str] = None) -> Optional[str]: +def default_language_code(default_service_language_code: str | None = None) -> str | None: if isinstance(default_service_language_code, str): return default_service_language_code.lower() elif isinstance(DEFAULT_GOOGLE_LANGUAGE, str): @@ -79,7 +79,7 @@ def default_language_code(default_service_language_code: Optional[str] = None) - return None -def default_country_code(default_service_country_code: Optional[str] = None) -> Optional[str]: +def default_country_code(default_service_country_code: str | None = None) -> str | None: if isinstance(default_service_country_code, str): return default_service_country_code.lower() elif isinstance(DEFAULT_GOOGLE_COUNTRY, str): @@ -88,9 +88,9 @@ def default_country_code(default_service_country_code: Optional[str] = None) -> def resolve_language_code( - language_code: Optional[str] = None, - default_service_language_code: Optional[str] = None, -) -> Optional[str]: + language_code: str | None = None, + default_service_language_code: str | None = None, +) -> str | None: language_code = language_code or default_language_code(default_service_language_code) if isinstance(language_code, str): @@ -102,9 +102,9 @@ def resolve_language_code( def resolve_country_code( - country_code: Optional[str] = None, - default_service_country_code: Optional[str] = None, -) -> Optional[str]: + country_code: str | None = None, + default_service_country_code: str | None = None, +) -> str | None: country_code = country_code or default_country_code(default_service_country_code) if isinstance(country_code, str): @@ -120,14 +120,14 @@ def resolve_country_code( # ------------------------------------------------------------------------------------------------ def get_google_maps_directions( context: ToolContext, - origin_address: Optional[str] = None, - destination_address: Optional[str] = None, - origin_latitude: Optional[str] = None, - origin_longitude: Optional[str] = None, - destination_latitude: Optional[str] = None, - destination_longitude: Optional[str] = None, - language: Optional[str] = DEFAULT_GOOGLE_MAPS_LANGUAGE, - country: Optional[str] = DEFAULT_GOOGLE_MAPS_COUNTRY, + origin_address: str | None = None, + destination_address: str | None = None, + origin_latitude: str | None = None, + origin_longitude: str | None = None, + destination_latitude: str | None = None, + destination_longitude: str | None = None, + language: str | None = DEFAULT_GOOGLE_MAPS_LANGUAGE, + country: str | None = DEFAULT_GOOGLE_MAPS_COUNTRY, distance_unit: GoogleMapsDistanceUnit = DEFAULT_GOOGLE_MAPS_DISTANCE_UNIT, travel_mode: GoogleMapsTravelMode = DEFAULT_GOOGLE_MAPS_TRAVEL_MODE, ) -> list[dict[str, Any]]: @@ -224,7 +224,7 @@ def clean_google_maps_direction(direction: dict[str, Any]) -> None: del stop["data_id"] -def enrich_google_maps_arrive_around(timestamp: Optional[int]) -> dict[str, Any]: +def enrich_google_maps_arrive_around(timestamp: int | None) -> dict[str, Any]: if not timestamp: return {} @@ -258,9 +258,7 @@ def parse_flight_results(results: dict[str, Any]) -> dict[str, Any]: # ------------------------------------------------------------------------------------------------ # Google News utils # ------------------------------------------------------------------------------------------------ -def extract_news_results( - results: dict[str, Any], limit: Optional[int] = None -) -> list[dict[str, Any]]: +def extract_news_results(results: dict[str, Any], limit: int | None = None) -> list[dict[str, Any]]: news_results = [] for result in results.get("news_results", []): news_results.append({ @@ -375,7 +373,7 @@ def extract_walmart_variant_options(variant_swatches: list[dict[str, Any]]) -> l # ------------------------------------------------------------------------------------------------ # YouTube utils # ------------------------------------------------------------------------------------------------ -def extract_video_id_from_link(link: Optional[str]) -> Optional[str]: +def extract_video_id_from_link(link: str | None) -> str | None: if not isinstance(link, str): return None @@ -387,7 +385,7 @@ def extract_video_id_from_link(link: Optional[str]) -> Optional[str]: def extract_video_description( video: dict[str, Any], max_description_length: int = YOUTUBE_MAX_DESCRIPTION_LENGTH, -) -> Optional[str]: +) -> str | None: description = video.get("description", "") if isinstance(description, dict): @@ -401,7 +399,7 @@ def extract_video_description( if description is not None: description = str(description).strip() - return cast(Optional[str], description) + return cast(str | None, description) def extract_video_results( diff --git a/toolkits/search/pyproject.toml b/toolkits/search/pyproject.toml index 62e3fd8c..0fd7ba64 100644 --- a/toolkits/search/pyproject.toml +++ b/toolkits/search/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_search" version = "1.4.0" -description = "Tools for searching the web" +description = "Arcade.dev LLM tools for searching the web" authors = ["Arcade "] [tool.poetry.dependencies] diff --git a/toolkits/slack/.ruff.toml b/toolkits/slack/.ruff.toml index bb64590d..19364180 100644 --- a/toolkits/slack/.ruff.toml +++ b/toolkits/slack/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/slack/Makefile b/toolkits/slack/Makefile index 8261337f..610095b0 100644 --- a/toolkits/slack/Makefile +++ b/toolkits/slack/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/slack/arcade_slack/custom_types.py b/toolkits/slack/arcade_slack/custom_types.py index c7827dce..9037bb18 100644 --- a/toolkits/slack/arcade_slack/custom_types.py +++ b/toolkits/slack/arcade_slack/custom_types.py @@ -1,8 +1,8 @@ -from typing import NewType, Optional, Union +from typing import NewType class PositiveInt(int): - def __new__(cls, value: Union[str, int], name: str = "value") -> "PositiveInt": + def __new__(cls, value: str | int, name: str = "value") -> "PositiveInt": def validate(val: int) -> int: if val <= 0: raise ValueError(f"{name} must be positive, got {val}") @@ -19,7 +19,7 @@ class PositiveInt(int): SlackOffsetSecondsFromUTC = NewType("SlackOffsetSecondsFromUTC", int) # observe it can be negative -SlackPaginationNextCursor = Optional[str] +SlackPaginationNextCursor = str | None SlackUserFieldId = NewType("SlackUserFieldId", str) SlackUserId = NewType("SlackUserId", str) SlackTeamId = NewType("SlackTeamId", str) diff --git a/toolkits/slack/arcade_slack/models.py b/toolkits/slack/arcade_slack/models.py index 69558dba..bfcb09cb 100644 --- a/toolkits/slack/arcade_slack/models.py +++ b/toolkits/slack/arcade_slack/models.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict from arcade_slack.custom_types import ( SlackOffsetSecondsFromUTC, @@ -45,15 +45,15 @@ class SlackUserFieldData(TypedDict, total=False): Slack type definition: https://api.slack.com/methods/users.profile.set#custom-profile """ - value: Optional[str] - alt: Optional[bool] + value: str | None + alt: bool | None class SlackStatusEmojiDisplayInfo(TypedDict, total=False): """Type definition for Slack status emoji display info dictionary.""" - emoji_name: Optional[str] - display_url: Optional[str] + emoji_name: str | None + display_url: str | None class SlackUserProfile(TypedDict, total=False): @@ -62,37 +62,37 @@ class SlackUserProfile(TypedDict, total=False): Slack type definition: https://api.slack.com/types/user#profile (https://archive.is/RUZdL) """ - title: Optional[str] - phone: Optional[str] - skype: Optional[str] - email: Optional[str] - real_name: Optional[str] - real_name_normalized: Optional[str] - display_name: Optional[str] - display_name_normalized: Optional[str] - first_name: Optional[str] - last_name: Optional[str] - fields: Optional[list[dict[SlackUserFieldId, SlackUserFieldData]]] - image_original: Optional[str] - is_custom_image: Optional[bool] - image_24: Optional[str] - image_32: Optional[str] - image_48: Optional[str] - image_72: Optional[str] - image_192: Optional[str] - image_512: Optional[str] - image_1024: Optional[str] - status_emoji: Optional[str] - status_emoji_display_info: Optional[list[SlackStatusEmojiDisplayInfo]] - status_text: Optional[str] - status_text_canonical: Optional[str] - status_expiration: Optional[int] - avatar_hash: Optional[str] - start_date: Optional[str] - pronouns: Optional[str] - huddle_state: Optional[str] - huddle_state_expiration: Optional[int] - team: Optional[SlackTeamId] + title: str | None + phone: str | None + skype: str | None + email: str | None + real_name: str | None + real_name_normalized: str | None + display_name: str | None + display_name_normalized: str | None + first_name: str | None + last_name: str | None + fields: list[dict[SlackUserFieldId, SlackUserFieldData]] | None + image_original: str | None + is_custom_image: bool | None + image_24: str | None + image_32: str | None + image_48: str | None + image_72: str | None + image_192: str | None + image_512: str | None + image_1024: str | None + status_emoji: str | None + status_emoji_display_info: list[SlackStatusEmojiDisplayInfo] | None + status_text: str | None + status_text_canonical: str | None + status_expiration: int | None + avatar_hash: str | None + start_date: str | None + pronouns: str | None + huddle_state: str | None + huddle_state_expiration: int | None + team: SlackTeamId | None class SlackUser(TypedDict, total=False): @@ -103,23 +103,23 @@ class SlackUser(TypedDict, total=False): id: SlackUserId team_id: SlackTeamId - name: Optional[str] - deleted: Optional[bool] - color: Optional[str] - real_name: Optional[str] - tz: Optional[str] - tz_label: Optional[str] - tz_offset: Optional[SlackOffsetSecondsFromUTC] - profile: Optional[SlackUserProfile] - is_admin: Optional[bool] - is_owner: Optional[bool] - is_primary_owner: Optional[bool] - is_restricted: Optional[bool] - is_ultra_restricted: Optional[bool] - is_bot: Optional[bool] - is_app_user: Optional[bool] - is_email_confirmed: Optional[bool] - who_can_share_contact_card: Optional[str] + name: str | None + deleted: bool | None + color: str | None + real_name: str | None + tz: str | None + tz_label: str | None + tz_offset: SlackOffsetSecondsFromUTC | None + profile: SlackUserProfile | None + is_admin: bool | None + is_owner: bool | None + is_primary_owner: bool | None + is_restricted: bool | None + is_ultra_restricted: bool | None + is_bot: bool | None + is_app_user: bool | None + is_email_confirmed: bool | None + who_can_share_contact_card: str | None class SlackUserList(TypedDict, total=False): @@ -131,25 +131,25 @@ class SlackUserList(TypedDict, total=False): class SlackConversationPurpose(TypedDict, total=False): """Type definition for the Slack conversation purpose dictionary.""" - value: Optional[str] + value: str | None class SlackConversation(TypedDict, total=False): """Type definition for the Slack conversation dictionary.""" - id: Optional[str] - name: Optional[str] - is_private: Optional[bool] - is_archived: Optional[bool] - is_member: Optional[bool] - is_channel: Optional[bool] - is_group: Optional[bool] - is_im: Optional[bool] - is_mpim: Optional[bool] - purpose: Optional[SlackConversationPurpose] - num_members: Optional[int] - user: Optional[SlackUser] - is_user_deleted: Optional[bool] + id: str | None + name: str | None + is_private: bool | None + is_archived: bool | None + is_member: bool | None + is_channel: bool | None + is_group: bool | None + is_im: bool | None + is_mpim: bool | None + purpose: SlackConversationPurpose | None + num_members: int | None + user: SlackUser | None + is_user_deleted: bool | None class SlackMessage(TypedDict, total=True): @@ -175,28 +175,28 @@ class Message(SlackMessage, total=False): class ConversationMetadata(TypedDict, total=False): """Type definition for the conversation metadata dictionary.""" - id: Optional[str] - name: Optional[str] - conversation_type: Optional[str] - is_private: Optional[bool] - is_archived: Optional[bool] - is_member: Optional[bool] - purpose: Optional[str] - num_members: Optional[int] - user: Optional[SlackUser] - is_user_deleted: Optional[bool] + id: str | None + name: str | None + conversation_type: str | None + is_private: bool | None + is_archived: bool | None + is_member: bool | None + purpose: str | None + num_members: int | None + user: SlackUser | None + is_user_deleted: bool | None class BasicUserInfo(TypedDict, total=False): """Type definition for the returned basic user info dictionary.""" - id: Optional[str] - name: Optional[str] - is_bot: Optional[bool] - email: Optional[str] - display_name: Optional[str] - real_name: Optional[str] - timezone: Optional[str] + id: str | None + name: str | None + is_bot: bool | None + email: str | None + display_name: str | None + real_name: str | None + timezone: str | None class SlackConversationsToolResponse(TypedDict, total=True): diff --git a/toolkits/slack/arcade_slack/tools/chat.py b/toolkits/slack/arcade_slack/tools/chat.py index f2c40581..1b882671 100644 --- a/toolkits/slack/arcade_slack/tools/chat.py +++ b/toolkits/slack/arcade_slack/tools/chat.py @@ -1,6 +1,6 @@ import asyncio from datetime import datetime, timezone -from typing import Annotated, Optional, cast +from typing import Annotated, cast from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Slack @@ -149,8 +149,8 @@ async def send_message_to_channel( async def get_members_in_conversation_by_id( context: ToolContext, conversation_id: Annotated[str, "The ID of the conversation to get members for"], - limit: Annotated[Optional[int], "The maximum number of members to return."] = None, - next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None, + limit: Annotated[int | None, "The maximum number of members to return."] = None, + next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None, ) -> Annotated[dict, "Information about each member in the conversation"]: """Get the members of a conversation in Slack by the conversation's ID.""" token = ( @@ -209,8 +209,8 @@ async def get_members_in_conversation_by_id( async def get_members_in_channel_by_name( context: ToolContext, channel_name: Annotated[str, "The name of the channel to get members for"], - limit: Annotated[Optional[int], "The maximum number of members to return."] = None, - next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None, + limit: Annotated[int | None, "The maximum number of members to return."] = None, + next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None, ) -> Annotated[dict, "The channel members' IDs and Names"]: """Get the members of a conversation in Slack by the conversation's name.""" channel = await get_channel_metadata_by_name(context=context, channel_name=channel_name) @@ -235,35 +235,35 @@ async def get_messages_in_conversation_by_id( context: ToolContext, conversation_id: Annotated[str, "The ID of the conversation to get history for"], oldest_relative: Annotated[ - Optional[str], + str | None, ( "The oldest message to include in the results, specified as a time offset from the " "current time in the format 'DD:HH:MM'" ), ] = None, latest_relative: Annotated[ - Optional[str], + str | None, ( "The latest message to include in the results, specified as a time offset from the " "current time in the format 'DD:HH:MM'" ), ] = None, oldest_datetime: Annotated[ - Optional[str], + str | None, ( "The oldest message to include in the results, specified as a datetime object in the " "format 'YYYY-MM-DD HH:MM:SS'" ), ] = None, latest_datetime: Annotated[ - Optional[str], + str | None, ( "The latest message to include in the results, specified as a datetime object in the " "format 'YYYY-MM-DD HH:MM:SS'" ), ] = None, - limit: Annotated[Optional[int], "The maximum number of messages to return."] = None, - next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None, + limit: Annotated[int | None, "The maximum number of messages to return."] = None, + next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None, ) -> Annotated[ dict, ( @@ -367,35 +367,35 @@ async def get_messages_in_channel_by_name( context: ToolContext, channel_name: Annotated[str, "The name of the channel"], oldest_relative: Annotated[ - Optional[str], + str | None, ( "The oldest message to include in the results, specified as a time offset from the " "current time in the format 'DD:HH:MM'" ), ] = None, latest_relative: Annotated[ - Optional[str], + str | None, ( "The latest message to include in the results, specified as a time offset from the " "current time in the format 'DD:HH:MM'" ), ] = None, oldest_datetime: Annotated[ - Optional[str], + str | None, ( "The oldest message to include in the results, specified as a datetime object in the " "format 'YYYY-MM-DD HH:MM:SS'" ), ] = None, latest_datetime: Annotated[ - Optional[str], + str | None, ( "The latest message to include in the results, specified as a datetime object in the " "format 'YYYY-MM-DD HH:MM:SS'" ), ] = None, - limit: Annotated[Optional[int], "The maximum number of messages to return."] = None, - next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None, + limit: Annotated[int | None, "The maximum number of messages to return."] = None, + next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None, ) -> Annotated[ dict, ( @@ -438,35 +438,35 @@ async def get_messages_in_direct_message_conversation_by_username( context: ToolContext, username: Annotated[str, "The username of the user to get messages from"], oldest_relative: Annotated[ - Optional[str], + str | None, ( "The oldest message to include in the results, specified as a time offset from the " "current time in the format 'DD:HH:MM'" ), ] = None, latest_relative: Annotated[ - Optional[str], + str | None, ( "The latest message to include in the results, specified as a time offset from the " "current time in the format 'DD:HH:MM'" ), ] = None, oldest_datetime: Annotated[ - Optional[str], + str | None, ( "The oldest message to include in the results, specified as a datetime object in the " "format 'YYYY-MM-DD HH:MM:SS'" ), ] = None, latest_datetime: Annotated[ - Optional[str], + str | None, ( "The latest message to include in the results, specified as a datetime object in the " "format 'YYYY-MM-DD HH:MM:SS'" ), ] = None, - limit: Annotated[Optional[int], "The maximum number of messages to return."] = None, - next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None, + limit: Annotated[int | None, "The maximum number of messages to return."] = None, + next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None, ) -> Annotated[ dict, ( @@ -511,35 +511,35 @@ async def get_messages_in_multi_person_dm_conversation_by_usernames( context: ToolContext, usernames: Annotated[list[str], "The usernames of the users to get messages from"], oldest_relative: Annotated[ - Optional[str], + str | None, ( "The oldest message to include in the results, specified as a time offset from the " "current time in the format 'DD:HH:MM'" ), ] = None, latest_relative: Annotated[ - Optional[str], + str | None, ( "The latest message to include in the results, specified as a time offset from the " "current time in the format 'DD:HH:MM'" ), ] = None, oldest_datetime: Annotated[ - Optional[str], + str | None, ( "The oldest message to include in the results, specified as a datetime object in the " "format 'YYYY-MM-DD HH:MM:SS'" ), ] = None, latest_datetime: Annotated[ - Optional[str], + str | None, ( "The latest message to include in the results, specified as a datetime object in the " "format 'YYYY-MM-DD HH:MM:SS'" ), ] = None, - limit: Annotated[Optional[int], "The maximum number of messages to return."] = None, - next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None, + limit: Annotated[int | None, "The maximum number of messages to return."] = None, + next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None, ) -> Annotated[ dict, ( @@ -629,7 +629,7 @@ async def get_channel_metadata_by_name( context: ToolContext, channel_name: Annotated[str, "The name of the channel to get metadata for"], next_cursor: Annotated[ - Optional[str], + str | None, "The cursor to use for pagination, if continuing from a previous search.", ] = None, ) -> Annotated[dict, "The channel metadata"]: @@ -698,11 +698,11 @@ async def get_direct_message_conversation_metadata_by_username( context: ToolContext, username: Annotated[str, "The username of the user/person to get messages with"], next_cursor: Annotated[ - Optional[str], + str | None, "The cursor to use for pagination, if continuing from a previous search.", ] = None, ) -> Annotated[ - Optional[dict], + dict | None, "The direct message conversation metadata.", ]: """Get the metadata of a direct message conversation in Slack by the username. @@ -750,11 +750,11 @@ async def get_multi_person_dm_conversation_metadata_by_usernames( context: ToolContext, usernames: Annotated[list[str], "The usernames of the users/people to get messages with"], next_cursor: Annotated[ - Optional[str], + str | None, "The cursor to use for pagination, if continuing from a previous search.", ] = None, ) -> Annotated[ - Optional[dict], + dict | None, "The multi-person direct message conversation metadata.", ]: """Get the metadata of a multi-person direct message conversation in Slack by the usernames. @@ -818,11 +818,11 @@ async def get_multi_person_dm_conversation_metadata_by_usernames( async def list_conversations_metadata( context: ToolContext, conversation_types: Annotated[ - Optional[list[ConversationType]], + list[ConversationType] | None, "The type(s) of conversations to list. Defaults to all types.", ] = None, - limit: Annotated[Optional[int], "The maximum number of conversations to list."] = None, - next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None, + limit: Annotated[int | None, "The maximum number of conversations to list."] = None, + next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None, ) -> Annotated[ dict, ( @@ -873,7 +873,7 @@ async def list_conversations_metadata( ) async def list_public_channels_metadata( context: ToolContext, - limit: Annotated[Optional[int], "The maximum number of channels to list."] = None, + limit: Annotated[int | None, "The maximum number of channels to list."] = None, ) -> Annotated[dict, "The public channels"]: """List metadata for public channels in Slack that the user is a member of.""" @@ -891,7 +891,7 @@ async def list_public_channels_metadata( ) async def list_private_channels_metadata( context: ToolContext, - limit: Annotated[Optional[int], "The maximum number of channels to list."] = None, + limit: Annotated[int | None, "The maximum number of channels to list."] = None, ) -> Annotated[dict, "The private channels"]: """List metadata for private channels in Slack that the user is a member of.""" @@ -909,7 +909,7 @@ async def list_private_channels_metadata( ) async def list_group_direct_message_conversations_metadata( context: ToolContext, - limit: Annotated[Optional[int], "The maximum number of conversations to list."] = None, + limit: Annotated[int | None, "The maximum number of conversations to list."] = None, ) -> Annotated[dict, "The group direct message conversations metadata"]: """List metadata for group direct message conversations that the user is a member of.""" @@ -929,7 +929,7 @@ async def list_group_direct_message_conversations_metadata( ) async def list_direct_message_conversations_metadata( context: ToolContext, - limit: Annotated[Optional[int], "The maximum number of conversations to list."] = None, + limit: Annotated[int | None, "The maximum number of conversations to list."] = None, ) -> Annotated[dict, "The direct message conversations metadata"]: """List metadata for direct message conversations in Slack that the user is a member of.""" diff --git a/toolkits/slack/arcade_slack/tools/users.py b/toolkits/slack/arcade_slack/tools/users.py index d63dd19b..4a071cc7 100644 --- a/toolkits/slack/arcade_slack/tools/users.py +++ b/toolkits/slack/arcade_slack/tools/users.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional, cast +from typing import Annotated, Any, cast from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Slack @@ -59,9 +59,9 @@ async def get_user_info_by_id( ) async def list_users( context: ToolContext, - exclude_bots: Annotated[Optional[bool], "Whether to exclude bots from the results"] = True, - limit: Annotated[Optional[int], "The maximum number of users to return."] = None, - next_cursor: Annotated[Optional[str], "The next cursor token to use for pagination."] = None, + exclude_bots: Annotated[bool | None, "Whether to exclude bots from the results"] = True, + limit: Annotated[int | None, "The maximum number of users to return."] = None, + next_cursor: Annotated[str | None, "The next cursor token to use for pagination."] = None, ) -> Annotated[dict, "The users' info"]: """List all users in the authenticated user's Slack team.""" diff --git a/toolkits/slack/arcade_slack/utils.py b/toolkits/slack/arcade_slack/utils.py index b2a38b4c..66647b22 100644 --- a/toolkits/slack/arcade_slack/utils.py +++ b/toolkits/slack/arcade_slack/utils.py @@ -1,6 +1,7 @@ import asyncio +from collections.abc import Callable from datetime import datetime, timezone -from typing import Any, Callable, Optional +from typing import Any from arcade.sdk import ToolContext from arcade.sdk.errors import RetryableToolError @@ -137,7 +138,7 @@ def extract_conversation_metadata(conversation: SlackConversation) -> Conversati """ conversation_type = get_slack_conversation_type_as_str(conversation) - purpose: Optional[SlackConversationPurpose] = conversation.get("purpose") + purpose: SlackConversationPurpose | None = conversation.get("purpose") purpose_value = "" if not purpose else purpose.get("value", "") metadata = ConversationMetadata( @@ -219,8 +220,8 @@ async def retrieve_conversations_by_user_ids( conversation_types: list[ConversationType], user_ids: list[str], exact_match: bool = False, - limit: Optional[int] = None, - next_cursor: Optional[str] = None, + limit: int | None = None, + next_cursor: str | None = None, ) -> list[dict]: """ Retrieve conversations filtered by the given user IDs. Includes pagination support @@ -312,13 +313,13 @@ def is_user_deleted(user: SlackUser) -> bool: async def async_paginate( func: Callable, - response_key: Optional[str] = None, - limit: Optional[int] = None, - next_cursor: Optional[SlackPaginationNextCursor] = None, + response_key: str | None = None, + limit: int | None = None, + next_cursor: SlackPaginationNextCursor | None = None, max_pagination_timeout_seconds: int = MAX_PAGINATION_TIMEOUT_SECONDS, *args: Any, **kwargs: Any, -) -> tuple[list, Optional[SlackPaginationNextCursor]]: +) -> tuple[list, SlackPaginationNextCursor | None]: """Paginate a Slack AsyncWebClient's method results. The purpose is to abstract the pagination work and make it easier for the LLM to retrieve the @@ -425,7 +426,7 @@ def convert_datetime_to_unix_timestamp(datetime_str: str) -> int: def convert_relative_datetime_to_unix_timestamp( relative_datetime: str, - current_unix_timestamp: Optional[int] = None, + current_unix_timestamp: int | None = None, ) -> int: """Convert a relative datetime string in the format 'DD:HH:MM' to unix timestamp. diff --git a/toolkits/slack/pyproject.toml b/toolkits/slack/pyproject.toml index db718dc5..5d9a10d4 100644 --- a/toolkits/slack/pyproject.toml +++ b/toolkits/slack/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_slack" version = "0.4.2" -description = "Slack tools for LLMs" +description = "Arcade.dev LLM tools for Slack" authors = ["Arcade "] [tool.poetry.dependencies] @@ -23,7 +23,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/spotify/.ruff.toml b/toolkits/spotify/.ruff.toml index bb64590d..19364180 100644 --- a/toolkits/spotify/.ruff.toml +++ b/toolkits/spotify/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/spotify/Makefile b/toolkits/spotify/Makefile index 51a56477..1602ac12 100644 --- a/toolkits/spotify/Makefile +++ b/toolkits/spotify/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/spotify/arcade_spotify/tools/models.py b/toolkits/spotify/arcade_spotify/tools/models.py index b2d48d43..66c809b3 100644 --- a/toolkits/spotify/arcade_spotify/tools/models.py +++ b/toolkits/spotify/arcade_spotify/tools/models.py @@ -1,33 +1,32 @@ from dataclasses import asdict, dataclass, field from enum import Enum -from typing import Optional @dataclass class PlaybackState: - is_playing: Optional[bool] = None - progress_ms: Optional[int] = ( + is_playing: bool | None = None + progress_ms: int | None = ( None # Progress into the currently playing track or episode in milliseconds ) - device_name: Optional[str] = None - device_id: Optional[str] = None - currently_playing_type: Optional[str] = None - album_id: Optional[str] = None - album_name: Optional[str] = None + device_name: str | None = None + device_id: str | None = None + currently_playing_type: str | None = None + album_id: str | None = None + album_name: str | None = None album_artists: list[str] = field(default_factory=list) - album_spotify_url: Optional[str] = None - track_id: Optional[str] = None - track_name: Optional[str] = None - track_spotify_url: Optional[str] = None + album_spotify_url: str | None = None + track_id: str | None = None + track_name: str | None = None + track_spotify_url: str | None = None track_artists: list[str] = field(default_factory=list) track_artists_ids: list[str] = field(default_factory=list) - show_name: Optional[str] = None - show_id: Optional[str] = None - show_spotify_url: Optional[str] = None - episode_name: Optional[str] = None - episode_id: Optional[str] = None - episode_spotify_url: Optional[str] = None - message: Optional[str] = None + show_name: str | None = None + show_id: str | None = None + show_spotify_url: str | None = None + episode_name: str | None = None + episode_id: str | None = None + episode_spotify_url: str | None = None + message: str | None = None def to_dict(self) -> dict: """Convert the PlaybackState instance to a dictionary, excluding None values.""" diff --git a/toolkits/spotify/arcade_spotify/tools/player.py b/toolkits/spotify/arcade_spotify/tools/player.py index 104af0e6..0787447e 100644 --- a/toolkits/spotify/arcade_spotify/tools/player.py +++ b/toolkits/spotify/arcade_spotify/tools/player.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import httpx from arcade.sdk import ToolContext, tool @@ -20,10 +20,10 @@ from arcade_spotify.tools.utils import ( async def adjust_playback_position( context: ToolContext, absolute_position_ms: Annotated[ - Optional[int], "The absolute position in milliseconds to seek to" + int | None, "The absolute position in milliseconds to seek to" ] = None, relative_position_ms: Annotated[ - Optional[int], + int | None, "The relative position from the current playback position in milliseconds to seek to", ] = None, ) -> Annotated[str, "Success/failure message"]: @@ -173,7 +173,7 @@ async def start_tracks_playback_by_id( "A list of Spotify track (song) IDs to play. Order of execution is not guarenteed.", ], position_ms: Annotated[ - Optional[int], + int | None, "The position in milliseconds to start the first track from", ] = 0, ) -> Annotated[str, "Success/failure message"]: @@ -270,7 +270,7 @@ async def play_artist_by_name( async def play_track_by_name( context: ToolContext, track_name: Annotated[str, "The name of the track to play"], - artist_name: Annotated[Optional[str], "The name of the artist of the track"] = None, + artist_name: Annotated[str | None, "The name of the artist of the track"] = None, ) -> Annotated[str, "Success/failure message"]: """Plays a song by name""" q = f"track:{track_name}" diff --git a/toolkits/spotify/pyproject.toml b/toolkits/spotify/pyproject.toml index f36f2102..bdccae23 100644 --- a/toolkits/spotify/pyproject.toml +++ b/toolkits/spotify/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_spotify" version = "0.2.1" -description = "Arcade tools for Spotify" +description = "Arcade.dev LLM tools for Spotify" authors = ["Arcade "] [tool.poetry.dependencies] @@ -20,7 +20,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/web/.ruff.toml b/toolkits/web/.ruff.toml index bb64590d..19364180 100644 --- a/toolkits/web/.ruff.toml +++ b/toolkits/web/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/web/Makefile b/toolkits/web/Makefile index ef5ab299..9b6f141d 100644 --- a/toolkits/web/Makefile +++ b/toolkits/web/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/web/arcade_web/tools/firecrawl.py b/toolkits/web/arcade_web/tools/firecrawl.py index e0052290..a9d0e1c6 100644 --- a/toolkits/web/arcade_web/tools/firecrawl.py +++ b/toolkits/web/arcade_web/tools/firecrawl.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any from arcade.sdk import ToolContext, tool from firecrawl import FirecrawlApp @@ -14,20 +14,20 @@ async def scrape_url( context: ToolContext, url: Annotated[str, "URL to scrape"], formats: Annotated[ - Optional[list[Formats]], "Formats to retrieve. Defaults to ['markdown']." + list[Formats] | None, "Formats to retrieve. Defaults to ['markdown']." ] = None, only_main_content: Annotated[ - Optional[bool], + bool | None, "Only return the main content of the page excluding headers, navs, footers, etc.", ] = True, include_tags: Annotated[list[str] | None, "List of tags to include in the output"] = None, exclude_tags: Annotated[list[str] | None, "List of tags to exclude from the output"] = None, wait_for: Annotated[ - Optional[int], + int | None, "Specify a delay in milliseconds before fetching the content, allowing the page " "sufficient time to load.", ] = 10, - timeout: Annotated[Optional[int], "Timeout in milliseconds for the request"] = 30000, + timeout: Annotated[int | None, "Timeout in milliseconds for the request"] = 30000, ) -> Annotated[dict[str, Any], "Scraped data in specified formats"]: """Scrape a URL using Firecrawl and return the data in specified formats.""" @@ -66,7 +66,7 @@ async def crawl_website( ] = False, allow_external_links: Annotated[bool, "Allow following links to external websites"] = False, webhook: Annotated[ - Optional[str], + str | None, "The URL to send a POST request to when the crawl is started, updated and completed.", ] = None, async_crawl: Annotated[bool, "Run the crawl asynchronously"] = True, @@ -163,7 +163,7 @@ async def cancel_crawl( async def map_website( context: ToolContext, url: Annotated[str, "The base URL to start crawling from"], - search: Annotated[Optional[str], "Search query to use for mapping"] = None, + search: Annotated[str | None, "Search query to use for mapping"] = None, ignore_sitemap: Annotated[bool, "Ignore the website sitemap when crawling"] = True, include_subdomains: Annotated[bool, "Include subdomains of the website"] = False, limit: Annotated[int, "Maximum number of links to return"] = 5000, diff --git a/toolkits/web/pyproject.toml b/toolkits/web/pyproject.toml index 161c394f..f304c4f5 100644 --- a/toolkits/web/pyproject.toml +++ b/toolkits/web/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_web" version = "1.0.1" -description = "LLM tools for web-related tasks" +description = "Arcade.dev LLM tools for web scraping related tasks" authors = ["Arcade "] [tool.poetry.dependencies] @@ -20,7 +20,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/x/.ruff.toml b/toolkits/x/.ruff.toml index bacd9161..f1aed90f 100644 --- a/toolkits/x/.ruff.toml +++ b/toolkits/x/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/x/Makefile b/toolkits/x/Makefile index 35030196..a69b9c21 100644 --- a/toolkits/x/Makefile +++ b/toolkits/x/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/x/arcade_x/tools/tweets.py b/toolkits/x/arcade_x/tools/tweets.py index 82ea3484..ade3e8b5 100644 --- a/toolkits/x/arcade_x/tools/tweets.py +++ b/toolkits/x/arcade_x/tools/tweets.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional +from typing import Annotated, Any import httpx from arcade.sdk import ToolContext, tool @@ -67,7 +67,7 @@ async def search_recent_tweets_by_username( int, "The maximum number of results to return. Must be in range [1, 100] inclusive" ] = 10, next_token: Annotated[ - Optional[str], "The pagination token starting from which to return results" + str | None, "The pagination token starting from which to return results" ] = None, ) -> Annotated[dict[str, Any], "Dictionary containing the search results"]: """Search for recent tweets (last 7 days) on X (Twitter) by username. @@ -121,7 +121,7 @@ async def search_recent_tweets_by_keywords( int, "The maximum number of results to return. Must be in range [1, 100] inclusive" ] = 10, next_token: Annotated[ - Optional[str], "The pagination token starting from which to return results" + str | None, "The pagination token starting from which to return results" ] = None, ) -> Annotated[dict[str, Any], "Dictionary containing the search results"]: """ diff --git a/toolkits/x/arcade_x/tools/utils.py b/toolkits/x/arcade_x/tools/utils.py index 7c9c9e8c..1f0f7b02 100644 --- a/toolkits/x/arcade_x/tools/utils.py +++ b/toolkits/x/arcade_x/tools/utils.py @@ -38,7 +38,9 @@ def parse_search_recent_tweets_response(response_data: dict[str, Any]) -> dict[s tweet["tweet_url"] = get_tweet_url(tweet["id"]) # Add 'author_username' and 'author_name' to each tweet - for tweet_data, user_data in zip(response_data["data"], response_data["includes"]["users"]): + for tweet_data, user_data in zip( + response_data["data"], response_data["includes"]["users"], strict=False + ): tweet_data["author_username"] = user_data["username"] tweet_data["author_name"] = user_data["name"] diff --git a/toolkits/x/pyproject.toml b/toolkits/x/pyproject.toml index 65b78a4a..adf04d89 100644 --- a/toolkits/x/pyproject.toml +++ b/toolkits/x/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_x" version = "0.1.12" -description = "LLM tools for interacting with X (Twitter)" +description = "Arcade.dev LLM tools for X (Twitter)" authors = ["Arcade "] [tool.poetry.dependencies] @@ -20,7 +20,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy] diff --git a/toolkits/zoom/.ruff.toml b/toolkits/zoom/.ruff.toml index bacd9161..f1aed90f 100644 --- a/toolkits/zoom/.ruff.toml +++ b/toolkits/zoom/.ruff.toml @@ -1,4 +1,4 @@ -target-version = "py39" +target-version = "py310" line-length = 100 fix = true diff --git a/toolkits/zoom/Makefile b/toolkits/zoom/Makefile index 3ded2851..b4222577 100644 --- a/toolkits/zoom/Makefile +++ b/toolkits/zoom/Makefile @@ -7,7 +7,12 @@ help: .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks @echo "📦 Checking if Poetry is installed" - @if ! command -v poetry &> /dev/null; then \ + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "📦 Poetry not found. Checking if pip is available"; \ + if ! command -v pip >/dev/null 2>&1; then \ + echo "❌ pip is not installed. Please install pip first."; \ + exit 1; \ + fi; \ echo "📦 Installing Poetry with pip"; \ pip install poetry==1.8.5; \ else \ diff --git a/toolkits/zoom/arcade_zoom/tools/meetings.py b/toolkits/zoom/arcade_zoom/tools/meetings.py index 1c9c2024..dd9c5e2e 100644 --- a/toolkits/zoom/arcade_zoom/tools/meetings.py +++ b/toolkits/zoom/arcade_zoom/tools/meetings.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Zoom @@ -14,7 +14,7 @@ from arcade_zoom.tools.utils import _handle_zoom_api_error, _send_zoom_request async def list_upcoming_meetings( context: ToolContext, user_id: Annotated[ - Optional[str], + str | None, "The user's user ID or email address. Defaults to 'me' for the current user.", ] = "me", ) -> Annotated[dict, "List of upcoming meetings within the next 24 hours"]: diff --git a/toolkits/zoom/pyproject.toml b/toolkits/zoom/pyproject.toml index 518d4596..f10456d8 100644 --- a/toolkits/zoom/pyproject.toml +++ b/toolkits/zoom/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "arcade_zoom" version = "0.1.10" -description = "Arcade tools for Zoom" +description = "Arcade.dev LLM tools for Zoom" authors = ["Arcade "] [tool.poetry.dependencies] @@ -20,7 +20,7 @@ tox = "^4.11.1" ruff = "^0.7.4" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0,<2.0.0"] build-backend = "poetry.core.masonry.api" [tool.mypy]