From 9ff114ecaa95d5b7c77bdf6cfc5a738325aadc6c Mon Sep 17 00:00:00 2001 From: Bence Gadanyi Date: Wed, 21 Jan 2026 13:46:34 +0000 Subject: [PATCH 1/3] chore: install pyrefly --- .pre-commit-config.yaml | 5 +++++ .vscode/eps-assist-me.code-workspace | 6 +++++- poetry.lock | 23 +++++++++++++++++++++-- pyproject.toml | 13 +++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b20e105d..b2b69ed8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,11 @@ repos: language_version: python3 args: [--line-length=120] + # - repo: https://github.com/facebook/pyrefly-pre-commit + # rev: v0.49.0 + # hooks: + # - id: pyrefly + - repo: local hooks: - id: lint-githubactions diff --git a/.vscode/eps-assist-me.code-workspace b/.vscode/eps-assist-me.code-workspace index fb738c98..4c43c5ef 100644 --- a/.vscode/eps-assist-me.code-workspace +++ b/.vscode/eps-assist-me.code-workspace @@ -107,7 +107,10 @@ "jest.nodeEnv": { "POWERTOOLS_DEV": true }, - "python.defaultInterpreterPath": "/workspaces/eps-assist-me/.venv/bin/python" + "python.defaultInterpreterPath": "/workspaces/eps-assist-me/.venv/bin/python", + "python.pyrefly.displayTypeErrors": "default", + "python.pyrefly.disableLanguageServices": false, + "python.analysis.showHoverGoToLinks": true }, "extensions": { "recommendations": [ @@ -115,6 +118,7 @@ "redhat.vscode-yaml", "ms-python.python", "ms-python.flake8", + "meta.pyrefly", "eamodio.gitlens", "github.vscode-pull-request-github", "orta.vscode-jest", diff --git a/poetry.lock b/poetry.lock index 6ed5d640..d2c239d9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1087,7 +1087,7 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -3150,6 +3150,25 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +[[package]] +name = "pyrefly" +version = "0.49.0" +description = "A fast type checker and language server for Python with powerful IDE features" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pyrefly-0.49.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1cd5516ddab7c745e195fe1470629251962498482025bf2a9a9d53d5bde73729"}, + {file = "pyrefly-0.49.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a998a37dc1465a648c03076545080a8bd2a421c67cac27686eca43244e8ac69"}, + {file = "pyrefly-0.49.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a96b1452fa61d7db6d5ae6b6297f50ba8c006ba7ce420233ebd33eaf95d04cfd"}, + {file = "pyrefly-0.49.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97f1b5fb1be6f8f4868fe40e7ebeed055c8483012212267e182d58a8e50723e7"}, + {file = "pyrefly-0.49.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ee11eefd1d551629ce1b25888814dbf758aac1a10279537d9425bc53f2d41c"}, + {file = "pyrefly-0.49.0-py3-none-win32.whl", hash = "sha256:6196cb9b20ee977f64fa1fe87e06d3f7a222c5155031d21139fc60464a7a4b9c"}, + {file = "pyrefly-0.49.0-py3-none-win_amd64.whl", hash = "sha256:15333b5550fd32a8f9a971ad124714d75f1906a67e48033dcc203258525bc7fd"}, + {file = "pyrefly-0.49.0-py3-none-win_arm64.whl", hash = "sha256:4a57eebced37836791b681626a4be004ebd27221bc208f8200e1e2ca8a8b9510"}, + {file = "pyrefly-0.49.0.tar.gz", hash = "sha256:d4e9a978d55253d2cd24c0354bd4cf087026d07bd374388c2ae12a3bc26f93fc"}, +] + [[package]] name = "pytest" version = "9.0.2" @@ -4055,4 +4074,4 @@ requests = "*" [metadata] lock-version = "2.1" python-versions = "^3.14" -content-hash = "f576efe5d99412c6dbf17b9fbbf394dc75220ed27a69f07e6a0d85d9e770c1b7" +content-hash = "bd558ad0796b41d29100f9d55c1fd130850530fe46fc9d335e04cffb37a29ab5" diff --git a/pyproject.toml b/pyproject.toml index 9d8c25de..3aba8aa2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ pytest-mock = "^3.15.1" pytest-cov = "^7.0.0" moto = {extras = ["ssm"], version = "^5.1.19"} markitdown = {extras = ["pdf", "docx", "pptx", "xlsx"], version = "^0.0.2"} +pyrefly = "^0.49.0" [tool.poetry.group.slackBotFunction.dependencies] @@ -52,3 +53,15 @@ aws-lambda-powertools = "^3.23.0" [tool.black] line-length = 120 + +[tool.pyrefly] +project-includes = [ + "**/*.py*", + "**/*.ipynb", +] +search-path = [ + "packages/slackBotFunction", + "packages/syncKnowledgeBaseFunction", + "packages/bedrockLoggingConfigFunction", + "packages/preprocessingFunction", +] From c43c4c6ab865f102aafefc969c246b71b1652b87 Mon Sep 17 00:00:00 2001 From: Bence Gadanyi Date: Wed, 21 Jan 2026 14:39:45 +0000 Subject: [PATCH 2/3] chore: surpress all current errors, addressing them later --- .../tests/test_handler.py | 2 ++ packages/slackBotFunction/app/handler.py | 1 + .../app/services/ai_processor.py | 3 +++ .../slackBotFunction/app/services/bedrock.py | 4 +++ .../slackBotFunction/app/services/dynamo.py | 1 + .../app/services/prompt_loader.py | 2 ++ .../app/services/query_reformulator.py | 6 ++++- .../slackBotFunction/app/services/slack.py | 1 + .../app/slack/slack_events.py | 26 +++++++++++++++++++ .../app/slack/slack_handlers.py | 2 ++ .../app/utils/handler_utils.py | 12 +++++++++ .../tests/test_direct_invocation.py | 13 ++++++++++ .../tests/test_forward_to_lambda.py | 10 ++++++- .../tests/test_handler_utils.py | 1 + .../slackBotFunction/tests/test_handlers.py | 5 ++++ .../tests/test_slack_commands.py | 1 + .../test_slack_events_citations.py | 24 +++++++++++++++++ .../test_slack_events_messages.py | 4 +++ .../syncKnowledgeBaseFunction/app/handler.py | 2 ++ .../tests/test_app.py | 1 + scripts/convert_docs_to_markdown.py | 1 + scripts/run_regression_tests.py | 1 + 22 files changed, 121 insertions(+), 2 deletions(-) diff --git a/packages/bedrockLoggingConfigFunction/tests/test_handler.py b/packages/bedrockLoggingConfigFunction/tests/test_handler.py index 43f32af5..cd509e45 100644 --- a/packages/bedrockLoggingConfigFunction/tests/test_handler.py +++ b/packages/bedrockLoggingConfigFunction/tests/test_handler.py @@ -6,6 +6,8 @@ import os import pytest from unittest.mock import patch, MagicMock + +# pyrefly: ignore [missing-module-attribute] from app.handler import handler, send_response diff --git a/packages/slackBotFunction/app/handler.py b/packages/slackBotFunction/app/handler.py index 099816bf..77d4f309 100644 --- a/packages/slackBotFunction/app/handler.py +++ b/packages/slackBotFunction/app/handler.py @@ -47,6 +47,7 @@ def handler(event: dict, context: LambdaContext) -> dict: """ # direct invocation bypasses slack infrastructure entirely if event.get("invocation_type") == "direct": + # pyrefly: ignore [bad-return] return handle_direct_invocation(event, context) app = get_app(logger=logger) diff --git a/packages/slackBotFunction/app/services/ai_processor.py b/packages/slackBotFunction/app/services/ai_processor.py index 857a9e24..aadffa59 100644 --- a/packages/slackBotFunction/app/services/ai_processor.py +++ b/packages/slackBotFunction/app/services/ai_processor.py @@ -19,6 +19,7 @@ def process_ai_query(user_query: str, session_id: str | None = None) -> AIProces reformulated_query = reformulate_query(user_query) # session_id enables conversation continuity across multiple queries + # pyrefly: ignore [bad-argument-type] kb_response = query_bedrock(reformulated_query, session_id) logger.info( @@ -29,6 +30,8 @@ def process_ai_query(user_query: str, session_id: str | None = None) -> AIProces return { "text": kb_response["output"]["text"], "session_id": kb_response.get("sessionId"), + # pyrefly: ignore [bad-typed-dict-key] "citations": kb_response.get("citations", []), + # pyrefly: ignore [bad-typed-dict-key] "kb_response": kb_response, # slack needs raw bedrock data for session handling } diff --git a/packages/slackBotFunction/app/services/bedrock.py b/packages/slackBotFunction/app/services/bedrock.py index 44d02019..21c1f3ff 100644 --- a/packages/slackBotFunction/app/services/bedrock.py +++ b/packages/slackBotFunction/app/services/bedrock.py @@ -12,6 +12,7 @@ logger = get_logger() +# pyrefly: ignore [bad-function-definition] def query_bedrock(user_query: str, session_id: str = None) -> RetrieveAndGenerateResponseTypeDef: """ Query Amazon Bedrock Knowledge Base using RAG (Retrieval-Augmented Generation) @@ -67,6 +68,7 @@ def query_bedrock(user_query: str, session_id: str = None) -> RetrieveAndGenerat if prompt_template: request_params["retrieveAndGenerateConfiguration"]["knowledgeBaseConfiguration"]["generationConfiguration"][ "promptTemplate" + # pyrefly: ignore [bad-typed-dict-key] ] = {"textPromptTemplate": prompt_template.get("prompt_text")} logger.info( "Using prompt template for RAG response generation", extra={"prompt_name": config.RAG_RESPONSE_PROMPT_NAME} @@ -74,12 +76,14 @@ def query_bedrock(user_query: str, session_id: str = None) -> RetrieveAndGenerat # Include session ID for conversation continuity across messages if session_id: + # pyrefly: ignore [bad-typed-dict-key] request_params["sessionId"] = session_id logger.info("Using existing session", extra={"session_id": session_id}) else: logger.info("Starting new conversation") logger.debug("Retrieve and Generate", extra={"params": request_params}) + # pyrefly: ignore [bad-argument-type] response = client.retrieve_and_generate(**request_params) logger.info( "Got Bedrock response", diff --git a/packages/slackBotFunction/app/services/dynamo.py b/packages/slackBotFunction/app/services/dynamo.py index fe9bb04c..fd3b966e 100644 --- a/packages/slackBotFunction/app/services/dynamo.py +++ b/packages/slackBotFunction/app/services/dynamo.py @@ -29,6 +29,7 @@ def get_state_information(key: dict[str, Any]) -> GetItemOutputTableTypeDef: return results +# pyrefly: ignore [bad-function-definition] def store_state_information(item: dict[str, Any], condition: str = None) -> None: start_time = time() table = get_slack_bot_state_table() diff --git a/packages/slackBotFunction/app/services/prompt_loader.py b/packages/slackBotFunction/app/services/prompt_loader.py index 10d941fb..3c30623f 100644 --- a/packages/slackBotFunction/app/services/prompt_loader.py +++ b/packages/slackBotFunction/app/services/prompt_loader.py @@ -71,6 +71,7 @@ def parse_system_message(chat_cfg: dict) -> str: return "\n\n".join(parts) +# pyrefly: ignore [bad-function-definition] def load_prompt(prompt_name: str, prompt_version: str = None) -> dict: """ Load a prompt template from Amazon Bedrock Prompt Management. @@ -106,6 +107,7 @@ def load_prompt(prompt_name: str, prompt_version: str = None) -> dict: # Extract and render the prompt template template_config = variant["templateConfiguration"] + # pyrefly: ignore [bad-argument-type] prompt_text = _render_prompt(template_config) actual_version = response.get("version", "DRAFT") diff --git a/packages/slackBotFunction/app/services/query_reformulator.py b/packages/slackBotFunction/app/services/query_reformulator.py index 4a2bd060..c3113576 100644 --- a/packages/slackBotFunction/app/services/query_reformulator.py +++ b/packages/slackBotFunction/app/services/query_reformulator.py @@ -38,9 +38,13 @@ def reformulate_query(user_query: str) -> str: ) # Format the prompt with the user query (using double braces from Bedrock template) + # pyrefly: ignore [missing-attribute] prompt = prompt_template.get("prompt_text").replace("{{user_query}}", user_query) result = invoke_model( - prompt=prompt, model_id=model_id, client=client, inference_config=prompt_template.get("inference_config") + prompt=prompt, + model_id=model_id, + client=client, + inference_config=prompt_template.get("inference_config", {}), ) reformulated_query = result["content"][0]["text"].strip() diff --git a/packages/slackBotFunction/app/services/slack.py b/packages/slackBotFunction/app/services/slack.py index 66fe280e..f502cd8b 100644 --- a/packages/slackBotFunction/app/services/slack.py +++ b/packages/slackBotFunction/app/services/slack.py @@ -12,6 +12,7 @@ def get_friendly_channel_name(channel_id: str, client: WebClient) -> str: try: conversations_info_response = client.conversations_info(channel=channel_id) if conversations_info_response["ok"]: + # pyrefly: ignore [unsupported-operation] friendly_channel_name = conversations_info_response["channel"]["name"] else: logger.warning( diff --git a/packages/slackBotFunction/app/slack/slack_events.py b/packages/slackBotFunction/app/slack/slack_events.py index f43ec723..948e573d 100644 --- a/packages/slackBotFunction/app/slack/slack_events.py +++ b/packages/slackBotFunction/app/slack/slack_events.py @@ -69,6 +69,7 @@ def cleanup_previous_unfeedback_qa( except ClientError as e: if e.response.get("Error", {}).get("Code") == "ConditionalCheckFailedException": + # pyrefly: ignore [unbound-name] logger.info("Q&A has feedback - keeping for user", extra={"message_ts": previous_message_ts}) else: logger.error("Error cleaning up Q&A", extra={"error": traceback.format_exc()}) @@ -163,8 +164,10 @@ def _create_feedback_blocks( # Feedback buttons blocks.append({"type": "divider", "block_id": "feedback-divider"}) + # pyrefly: ignore [bad-argument-type] blocks.append({"type": "context", "elements": [{"type": "mrkdwn", "text": "Was this response helpful?"}]}) blocks.append( + # pyrefly: ignore [bad-argument-type] { "type": "actions", "block_id": "feedback_block", @@ -201,6 +204,7 @@ def _create_response_body(citations: list[dict[str, str]], feedback_data: dict[s result = _create_citation(citation, feedback_data, response_text) action_buttons += result.get("action_buttons", []) + # pyrefly: ignore [bad-assignment] response_text = result.get("response_text", response_text) # Remove any citations that have not been returned @@ -213,6 +217,7 @@ def _create_response_body(citations: list[dict[str, str]], feedback_data: dict[s # Citation action block if action_buttons: blocks.append( + # pyrefly: ignore [bad-argument-type] { "type": "actions", "block_id": "citation_actions", @@ -400,6 +405,7 @@ def process_async_slack_event(event: Dict[str, Any], event_id: str, client: WebC pull_request_id, _ = extract_pull_request_id(text=message_text) forward_to_pull_request_lambda( body={}, + # pyrefly: ignore [bad-argument-type] pull_request_id=pull_request_id, event=event, event_id=event_id, @@ -453,6 +459,7 @@ def process_pull_request_slack_event(slack_event_data: Dict[str, Any]) -> None: process_async_slack_event(event=event, event_id=event_id, client=client) except Exception: # we cant post a reply to slack for this error as we may not have details about where to post it + # pyrefly: ignore [unbound-name] logger.error(processing_error_message, extra={"event_id": event_id, "error": traceback.format_exc()}) @@ -535,18 +542,23 @@ def process_slack_message(event: Dict[str, Any], event_id: str, client: WebClien ) _handle_session_management( + # pyrefly: ignore [bad-argument-type] **feedback_data, session_data=session_data, + # pyrefly: ignore [bad-argument-type] session_id=session_id, + # pyrefly: ignore [bad-argument-type] kb_response=kb_response, user_id=user_id, conversation_key=conversation_key, ) # Store Q&A pair for feedback correlation + # pyrefly: ignore [bad-argument-type, missing-attribute] store_qa_pair(conversation_key, user_query, response_text, message_ts, kb_response.get("sessionId"), user_id) try: + # pyrefly: ignore [bad-argument-type] client.chat_update(channel=channel, ts=message_ts, text=response_text, blocks=blocks) except Exception as e: logger.error( @@ -558,6 +570,7 @@ def process_slack_message(event: Dict[str, Any], event_id: str, client: WebClien logger.error(f"Error processing message: {e}", extra={"event_id": event_id, "error": traceback.format_exc()}) # Try to notify user of error via Slack + # pyrefly: ignore [unbound-name] post_error_message(channel=channel, thread_ts=thread_ts, client=client) @@ -650,6 +663,7 @@ def store_feedback( if feedback_text: feedback_item["feedback_text"] = feedback_text[:4000] + # pyrefly: ignore [bad-argument-type] store_state_information(item=feedback_item, condition=condition) # Mark Q&A as having received feedback to prevent deletion @@ -705,6 +719,7 @@ def process_formatted_bedrock_query( blocks = _create_feedback_blocks(response_text, citations, feedback_data) + # pyrefly: ignore [bad-return] return kb_response, response_text, blocks @@ -715,6 +730,7 @@ def open_citation(channel: str, timestamp: str, message: Any, params: Dict[str, # Citation details title: str = params.get("title", "No title available.").strip() body: str = params.get("body", "No citation text available.").strip() + # pyrefly: ignore [bad-assignment] source_number: str = params.get("source_number") # Remove any existing citation block/divider @@ -792,6 +808,7 @@ def _toggle_button_style(element: dict) -> bool: def process_command_test_request(command: Dict[str, Any], client: WebClient) -> None: + # pyrefly: ignore [not-iterable] if "help" in command.get("text"): process_command_test_help(command=command, client=client) else: @@ -806,6 +823,7 @@ def process_command_test_questions(command: Dict[str, Any], client: WebClient) - logger.info(acknowledgement_msg, extra={"command": command}) # Extract parameters + # pyrefly: ignore [bad-argument-type] params = extract_test_command_params(command.get("text")) # Is the command targeting a PR @@ -836,6 +854,7 @@ def process_command_test_questions(command: Dict[str, Any], client: WebClient) - "channel": command["channel_id"], "text": acknowledgement_msg, } + # pyrefly: ignore [bad-argument-type] client.chat_postEphemeral(**post_params, user=command.get("user_id")) # Retrieve sample questions @@ -857,12 +876,14 @@ def process_command_test_questions(command: Dict[str, Any], client: WebClient) - process_command_test_ai_request, question=question, response=response, # Pass the response object we just got + # pyrefly: ignore [bad-argument-type] output=output, client=client, ) futures.append(future) post_params["text"] = "Testing complete, generating file..." + # pyrefly: ignore [bad-argument-type] client.chat_postEphemeral(**post_params, user=command.get("user_id")) aggregated_results = [] @@ -974,9 +995,11 @@ def get_conversation_session_data(conversation_key: str) -> Dict[str, Any]: if "Item" in response: logger.info("Found existing session", extra={"conversation_key": conversation_key}) return response["Item"] + # pyrefly: ignore [bad-return] return None except Exception: logger.error("Error getting session", extra={"error": traceback.format_exc()}) + # pyrefly: ignore [bad-return] return None @@ -987,6 +1010,7 @@ def get_latest_message_ts(conversation_key: str) -> str | None: try: response = get_state_information(key={"pk": conversation_key, "sk": constants.SESSION_SK}) if "Item" in response: + # pyrefly: ignore [bad-return] return response["Item"].get("latest_message_ts") return None except Exception: @@ -999,7 +1023,9 @@ def store_conversation_session( session_id: str, user_id: str, channel_id: str, + # pyrefly: ignore [bad-function-definition] thread_ts: str = None, + # pyrefly: ignore [bad-function-definition] latest_message_ts: str = None, ) -> None: """ diff --git a/packages/slackBotFunction/app/slack/slack_handlers.py b/packages/slackBotFunction/app/slack/slack_handlers.py index 734ac2b6..7abe789c 100644 --- a/packages/slackBotFunction/app/slack/slack_handlers.py +++ b/packages/slackBotFunction/app/slack/slack_handlers.py @@ -87,6 +87,7 @@ def feedback_handler(body: Dict[str, Any], client: WebClient) -> None: ) forward_to_pull_request_lambda( body=body, + # pyrefly: ignore [bad-argument-type] event=None, event_id="", store_pull_request_id=False, @@ -157,6 +158,7 @@ def command_handler(body: Dict[str, Any], command: Dict[str, Any], client: WebCl return user_id = command.get("user_id") + # pyrefly: ignore [bad-argument-type] session_pull_request_id = extract_test_command_params(command.get("text")).get("pr") if session_pull_request_id: logger.info(f"Command in pull request session {session_pull_request_id} from user {user_id}") diff --git a/packages/slackBotFunction/app/utils/handler_utils.py b/packages/slackBotFunction/app/utils/handler_utils.py index 44ebc0b1..e72b0256 100644 --- a/packages/slackBotFunction/app/utils/handler_utils.py +++ b/packages/slackBotFunction/app/utils/handler_utils.py @@ -61,6 +61,7 @@ def get_pull_request_lambda_arn(pull_request_id: str) -> str: response = cloudformation_client.describe_stacks(StackName=f"epsam-pr-{pull_request_id}") outputs = {o["OutputKey"]: o["OutputValue"] for o in response["Stacks"][0]["Outputs"]} pull_request_lambda_arn = outputs.get("SlackBotLambdaArn") + # pyrefly: ignore [bad-return] return pull_request_lambda_arn except Exception as e: logger.error("Failed to get pull request lambda arn", extra={"error": traceback.format_exc()}) @@ -104,6 +105,7 @@ def get_forward_payload(body: Dict[str, Any], event: Dict[str, Any], event_id: s if event_id is None or event["text"] is None: logger.error("Missing required fields to forward pull request event") + # pyrefly: ignore [bad-return] return None message_text = event["text"] @@ -214,7 +216,9 @@ def conversation_key_and_root(event: Dict[str, Any]) -> Tuple[str, str]: channel_id = event["channel"] root = event.get("thread_ts") or event.get("ts") if event.get("channel_type") == constants.CHANNEL_TYPE_IM: + # pyrefly: ignore [bad-return] return f"{constants.DM_PREFIX}{channel_id}#{root}", root + # pyrefly: ignore [bad-return] return f"{constants.THREAD_PREFIX}{channel_id}#{root}", root @@ -226,6 +230,7 @@ def extract_session_pull_request_id(conversation_key: str) -> str | None: if "Item" in response: logger.info("Found existing pull request session", extra={"conversation_key": conversation_key}) logger.debug("response", extra={"response": response}) + # pyrefly: ignore [bad-return] return response["Item"]["pull_request_id"] return None except Exception as e: @@ -239,17 +244,21 @@ def get_bot_user_id(client: WebClient) -> str | None: This is cached to avoid repeated API calls. """ if not hasattr(get_bot_user_id, "_cache"): + # pyrefly: ignore [missing-attribute] get_bot_user_id._cache = {} cache_key = id(client) + # pyrefly: ignore [missing-attribute] if cache_key in get_bot_user_id._cache: + # pyrefly: ignore [missing-attribute] return get_bot_user_id._cache[cache_key] try: auth_response = client.auth_test() if auth_response.get("ok"): bot_user_id = auth_response.get("user_id") + # pyrefly: ignore [missing-attribute] get_bot_user_id._cache[cache_key] = bot_user_id logger.info("Cached bot user ID", extra={"bot_user_id": bot_user_id}) return bot_user_id @@ -282,6 +291,7 @@ def was_bot_mentioned_in_thread_root(channel: str, thread_ts: str, client: WebCl # check if THIS bot is mentioned in any message in the thread bot_mention_pattern = rf"<@{re.escape(bot_user_id)}(?:\|[^>]+)?>" + # pyrefly: ignore [not-iterable] for message in response["messages"]: message_text = message.get("text", "") if re.search(bot_mention_pattern, message_text): @@ -307,6 +317,7 @@ def was_bot_mentioned_in_thread_root(channel: str, thread_ts: str, client: WebCl return True +# pyrefly: ignore [bad-function-definition] def should_reply_to_message(event: Dict[str, Any], client: WebClient = None) -> bool: """ Determine if the bot should reply to the message. @@ -331,6 +342,7 @@ def should_reply_to_message(event: Dict[str, Any], client: WebClient = None) -> channel = event.get("channel") thread_ts = event.get("thread_ts") + # pyrefly: ignore [bad-argument-type] if not was_bot_mentioned_in_thread_root(channel, thread_ts, client): logger.debug( "Bot not mentioned in thread, ignoring message", extra={"channel": channel, "thread_ts": thread_ts} diff --git a/packages/slackBotFunction/tests/test_direct_invocation.py b/packages/slackBotFunction/tests/test_direct_invocation.py index dba34e8d..3348407d 100644 --- a/packages/slackBotFunction/tests/test_direct_invocation.py +++ b/packages/slackBotFunction/tests/test_direct_invocation.py @@ -22,11 +22,15 @@ def test_successful_direct_invocation_without_session(self, mock_process_ai_quer event: DirectInvocationRequest = {"invocation_type": "direct", "query": "How do I authenticate with EPS API?"} + # pyrefly: ignore [bad-argument-type] result: DirectInvocationResponse = handle_direct_invocation(event, Mock()) assert result["statusCode"] == 200 + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["text"] == "AI response about EPS API authentication" + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["session_id"] == "new-session-123" + # pyrefly: ignore [bad-typed-dict-key] assert len(result["response"]["citations"]) == 1 assert "timestamp" in result["response"] @@ -47,8 +51,11 @@ def test_successful_direct_invocation_with_session(self, mock_process_ai_query): result: DirectInvocationResponse = handle_direct_invocation(event, Mock()) assert result["statusCode"] == 200 + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["text"] == "Follow-up response" + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["session_id"] == "existing-session-456" + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["citations"] == [] assert "timestamp" in result["response"] @@ -61,6 +68,7 @@ def test_direct_invocation_missing_query(self): result = handle_direct_invocation(event, Mock()) assert result["statusCode"] == 400 + # pyrefly: ignore [bad-typed-dict-key] assert "Missing required field: query" in result["response"]["error"] assert "timestamp" in result["response"] @@ -71,6 +79,7 @@ def test_direct_invocation_empty_query(self): result = handle_direct_invocation(event, Mock()) assert result["statusCode"] == 400 + # pyrefly: ignore [bad-typed-dict-key] assert "Missing required field: query" in result["response"]["error"] assert "timestamp" in result["response"] @@ -84,6 +93,7 @@ def test_direct_invocation_processing_error(self, mock_process_ai_query): result = handle_direct_invocation(event, Mock()) assert result["statusCode"] == 500 + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["error"] == "Internal server error" assert "timestamp" in result["response"] @@ -95,6 +105,7 @@ def test_direct_invocation_with_none_query(self, mock_process_ai_query): result = handle_direct_invocation(event, Mock()) assert result["statusCode"] == 400 + # pyrefly: ignore [bad-typed-dict-key] assert "Missing required field: query" in result["response"]["error"] @patch("app.services.ai_processor.process_ai_query") @@ -105,6 +116,7 @@ def test_direct_invocation_whitespace_query(self, mock_process_ai_query): result = handle_direct_invocation(event, Mock()) assert result["statusCode"] == 400 + # pyrefly: ignore [bad-typed-dict-key] assert "Missing required field: query" in result["response"]["error"] @patch("app.services.ai_processor.process_ai_query") @@ -133,6 +145,7 @@ def test_direct_invocation_response_structure(self, mock_process_ai_query): assert "timestamp" in result["response"] # citation passthrough: bedrock data structure preserved + # pyrefly: ignore [bad-typed-dict-key] assert len(result["response"]["citations"]) == 2 assert result["response"]["citations"][0]["title"] == "Doc 1" assert result["response"]["citations"][1]["uri"] == "https://example.com/2" diff --git a/packages/slackBotFunction/tests/test_forward_to_lambda.py b/packages/slackBotFunction/tests/test_forward_to_lambda.py index e4eccaf7..b6d6127c 100644 --- a/packages/slackBotFunction/tests/test_forward_to_lambda.py +++ b/packages/slackBotFunction/tests/test_forward_to_lambda.py @@ -175,6 +175,7 @@ def client_side_effect(service_name, *args, **kwargs): pull_request_id="123", body=mock_body, type="action", + # pyrefly: ignore [bad-argument-type] event=None, event_id="", store_pull_request_id=False, @@ -212,7 +213,14 @@ def client_side_effect(service_name, *args, **kwargs): with patch("app.utils.handler_utils.get_pull_request_lambda_arn", return_value="output_SlackBotLambdaArn"): forward_to_pull_request_lambda( - pull_request_id="123", body=mock_body, type="action", event=None, event_id="", store_pull_request_id=False + # pyrefly: ignore [bad-argument-type] + pull_request_id="123", + body=mock_body, + type="action", + # pyrefly: ignore [bad-argument-type] + event=None, + event_id="", + store_pull_request_id=False, ) # assertions diff --git a/packages/slackBotFunction/tests/test_handler_utils.py b/packages/slackBotFunction/tests/test_handler_utils.py index c2ecb19f..87960b3a 100644 --- a/packages/slackBotFunction/tests/test_handler_utils.py +++ b/packages/slackBotFunction/tests/test_handler_utils.py @@ -482,6 +482,7 @@ def test_should_reply_to_message_channel_thread_no_client(mock_env: Mock): from app.utils.handler_utils import should_reply_to_message event = {"channel_type": "channel", "type": "message", "channel": "C123", "thread_ts": "1234567890.123456"} + # pyrefly: ignore [bad-argument-type] result = should_reply_to_message(event, None) assert result is True diff --git a/packages/slackBotFunction/tests/test_handlers.py b/packages/slackBotFunction/tests/test_handlers.py index a8e7522e..e87b8fb1 100644 --- a/packages/slackBotFunction/tests/test_handlers.py +++ b/packages/slackBotFunction/tests/test_handlers.py @@ -273,8 +273,11 @@ def test_handle_direct_invocation_success( # verify response structure and ai service integration assert result["statusCode"] == 200 + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["text"] == "Authentication requires OAuth 2.0..." + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["session_id"] == "new-session-456" + # pyrefly: ignore [bad-typed-dict-key] assert len(result["response"]["citations"]) == 1 assert "timestamp" in result["response"] mock_process_ai_query.assert_called_once_with("How do I authenticate with EPS API?", None) @@ -297,6 +300,7 @@ def test_handle_direct_invocation_missing_query( # verify validation error with proper http status assert result["statusCode"] == 400 + # pyrefly: ignore [bad-typed-dict-key] assert "Missing required field: query" in result["response"]["error"] assert "timestamp" in result["response"] @@ -323,5 +327,6 @@ def test_handle_direct_invocation_processing_error( # verify 500 error with generic message - no internal details leaked assert result["statusCode"] == 500 + # pyrefly: ignore [bad-typed-dict-key] assert result["response"]["error"] == "Internal server error" assert "timestamp" in result["response"] diff --git a/packages/slackBotFunction/tests/test_slack_commands.py b/packages/slackBotFunction/tests/test_slack_commands.py index dbff1c8c..575f987a 100644 --- a/packages/slackBotFunction/tests/test_slack_commands.py +++ b/packages/slackBotFunction/tests/test_slack_commands.py @@ -92,6 +92,7 @@ def test_process_slack_command_handler_no_command( del sys.modules["app.slack.slack_handlers"] from app.slack.slack_handlers import command_handler + # pyrefly: ignore [bad-argument-type] command_handler(slack_command_data, slack_command_data, mock_client) # assertions diff --git a/packages/slackBotFunction/tests/test_slack_events/test_slack_events_citations.py b/packages/slackBotFunction/tests/test_slack_events/test_slack_events_citations.py index f054b483..acd22ff9 100644 --- a/packages/slackBotFunction/tests/test_slack_events/test_slack_events_citations.py +++ b/packages/slackBotFunction/tests/test_slack_events/test_slack_events_citations.py @@ -367,6 +367,7 @@ def test_create_response_body_creates_body_without_citations( # assertions assert len(response) > 0 assert response[0]["type"] == "section" + # pyrefly: ignore [bad-index] assert "This is a response without a citation." in response[0]["text"]["text"] @@ -399,9 +400,13 @@ def test_create_response_body_update_body_with_citations( assert response[1]["type"] == "actions" assert response[1]["block_id"] == "citation_actions" + # pyrefly: ignore [bad-typed-dict-key] citation_element = response[1]["elements"][0] + # pyrefly: ignore [bad-index] assert citation_element["type"] == "button" + # pyrefly: ignore [bad-index] assert citation_element["action_id"] == "cite_1" + # pyrefly: ignore [bad-index] assert "[1] Citation Title" in citation_element["text"]["text"] @@ -440,14 +445,22 @@ def test_create_response_body_creates_body_with_multiple_citations( assert response[1]["type"] == "actions" assert response[1]["block_id"] == "citation_actions" + # pyrefly: ignore [bad-typed-dict-key] first_citation_element = response[1]["elements"][0] + # pyrefly: ignore [bad-index] assert first_citation_element["type"] == "button" + # pyrefly: ignore [bad-index] assert first_citation_element["action_id"] == "cite_1" + # pyrefly: ignore [bad-index] assert "[1] Citation Title" in first_citation_element["text"]["text"] + # pyrefly: ignore [bad-typed-dict-key] second_citation_element = response[1]["elements"][1] + # pyrefly: ignore [bad-index] assert second_citation_element["type"] == "button" + # pyrefly: ignore [bad-index] assert second_citation_element["action_id"] == "cite_2" + # pyrefly: ignore [bad-index] assert "[2] Citation Title" in second_citation_element["text"]["text"] @@ -489,9 +502,13 @@ def test_create_response_body_creates_body_ignoring_low_score_citations( citation_elements = response[1]["elements"] assert len(citation_elements) == 1 + # pyrefly: ignore [bad-typed-dict-key] citation_element = citation_elements[0] + # pyrefly: ignore [bad-index] assert citation_element["type"] == "button" + # pyrefly: ignore [bad-index] assert citation_element["action_id"] == "cite_2" + # pyrefly: ignore [bad-index] assert "[2] Citation Title" in citation_element["text"]["text"] @@ -522,6 +539,7 @@ def test_create_response_body_update_body_with_reformatted_citations( # assertions assert len(response) > 1 assert response[0]["type"] == "section" + # pyrefly: ignore [bad-index] assert "This is a response with a citation.[1]" in response[0]["text"]["text"] @@ -554,7 +572,9 @@ def test_create_response_body_creates_body_with_markdown_formatting( assert response[1]["type"] == "actions" assert response[1]["block_id"] == "citation_actions" + # pyrefly: ignore [bad-typed-dict-key] citation_element = response[1]["elements"][0] + # pyrefly: ignore [bad-index] citation_value = json.loads(citation_element["value"]) assert "*Bold*, _italics_, and `code`." in citation_value.get("body") @@ -591,7 +611,9 @@ def test_create_response_body_creates_body_with_lists( assert response[1]["type"] == "actions" assert response[1]["block_id"] == "citation_actions" + # pyrefly: ignore [bad-typed-dict-key] citation_element = response[1]["elements"][0] + # pyrefly: ignore [bad-index] citation_value = json.loads(citation_element["value"]) expected_output = "Header text\n- Standard Dash\n- No Space Dash\n- Standard Bullet\n- NoSpace-NoSpace" @@ -627,7 +649,9 @@ def test_create_response_body_creates_body_without_encoding_errors( assert response[1]["type"] == "actions" assert response[1]["block_id"] == "citation_actions" + # pyrefly: ignore [bad-typed-dict-key] citation_element = response[1]["elements"][0] + # pyrefly: ignore [bad-index] citation_value = json.loads(citation_element["value"]) assert "Tabbing Issue.\n- Bullet point issue." in citation_value.get("body") diff --git a/packages/slackBotFunction/tests/test_slack_events/test_slack_events_messages.py b/packages/slackBotFunction/tests/test_slack_events/test_slack_events_messages.py index 60d52c7d..eb0efd0d 100644 --- a/packages/slackBotFunction/tests/test_slack_events/test_slack_events_messages.py +++ b/packages/slackBotFunction/tests/test_slack_events/test_slack_events_messages.py @@ -175,6 +175,7 @@ def test_process_slack_message_with_thread_ts( "ts": "1234567890.123", "thread_ts": "1234567888.111", # Existing thread } + # pyrefly: ignore [unbound-name] process_slack_message(event=slack_event_data, event_id="evt123", client=mock_client) # assertions @@ -451,6 +452,7 @@ def test_create_response_body_creates_body_with_markdown_formatting( assert len(response) > 0 assert response[0]["type"] == "section" + # pyrefly: ignore [bad-index] response_value = response[0]["text"]["text"] assert "*Bold*, _italics_, and `code`." in response_value @@ -479,6 +481,7 @@ def test_create_response_body_creates_body_with_lists( assert len(response) > 0 assert response[0]["type"] == "section" + # pyrefly: ignore [bad-index] response_value = response[0]["text"]["text"] expected_output = "Header text\n- Standard Dash\n- No Space Dash\n- Standard Bullet\n- NoSpace-NoSpace" @@ -506,6 +509,7 @@ def test_create_response_body_creates_body_without_encoding_errors( assert len(response) > 0 assert response[0]["type"] == "section" + # pyrefly: ignore [bad-index] response_value = response[0]["text"]["text"] assert "Tabbing Issue.\n- Bullet point issue." in response_value diff --git a/packages/syncKnowledgeBaseFunction/app/handler.py b/packages/syncKnowledgeBaseFunction/app/handler.py index fbc923e0..6f4c02c3 100644 --- a/packages/syncKnowledgeBaseFunction/app/handler.py +++ b/packages/syncKnowledgeBaseFunction/app/handler.py @@ -10,6 +10,8 @@ import traceback import boto3 from botocore.exceptions import ClientError + +# pyrefly: ignore [missing-import] from app.config.config import KNOWLEDGEBASE_ID, DATA_SOURCE_ID, SUPPORTED_FILE_TYPES, logger diff --git a/packages/syncKnowledgeBaseFunction/tests/test_app.py b/packages/syncKnowledgeBaseFunction/tests/test_app.py index 86046e60..d4cc67f5 100644 --- a/packages/syncKnowledgeBaseFunction/tests/test_app.py +++ b/packages/syncKnowledgeBaseFunction/tests/test_app.py @@ -253,6 +253,7 @@ def test_handler_empty_records(mock_env, lambda_context): ) def test_is_supported_file_type(filename, expected): """Test file type allowlist validation""" + # pyrefly: ignore [missing-module-attribute] from app.handler import is_supported_file_type assert is_supported_file_type(filename) is expected diff --git a/scripts/convert_docs_to_markdown.py b/scripts/convert_docs_to_markdown.py index eb3db510..3c51d695 100755 --- a/scripts/convert_docs_to_markdown.py +++ b/scripts/convert_docs_to_markdown.py @@ -46,6 +46,7 @@ def convert_document_to_markdown(input_path: Path, output_path: Path) -> bool: return False +# pyrefly: ignore [bad-function-definition] def convert_all_documents(raw_docs_dir: Path, sample_docs_dir: Path, specific_file: str = None) -> tuple[int, int]: """ batch-convert docs in raw_docs_dir into markdown diff --git a/scripts/run_regression_tests.py b/scripts/run_regression_tests.py index 772c6a5f..add9aa0e 100644 --- a/scripts/run_regression_tests.py +++ b/scripts/run_regression_tests.py @@ -27,6 +27,7 @@ } +# pyrefly: ignore [implicit-import] class BearerAuth(requests.auth.AuthBase): def __init__(self, token): self.token = token From ef6a99c74abbdc8dd6528c3c6e4de40439a0e4ba Mon Sep 17 00:00:00 2001 From: Bence Gadanyi Date: Wed, 21 Jan 2026 14:44:05 +0000 Subject: [PATCH 3/3] chore: enable pyrefly in precommit --- .pre-commit-config.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b2b69ed8..2d0ee122 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,10 +28,13 @@ repos: language_version: python3 args: [--line-length=120] - # - repo: https://github.com/facebook/pyrefly-pre-commit - # rev: v0.49.0 - # hooks: - # - id: pyrefly + - repo: https://github.com/facebook/pyrefly-pre-commit + rev: 0.49.0 + hooks: + - id: pyrefly-check + name: Pyrefly (type checking) + pass_filenames: false + language: system - repo: local hooks: