MAINT: enable strict mypy checking and fix violations#1515
MAINT: enable strict mypy checking and fix violations#1515tejas0077 wants to merge 29 commits intomicrosoft:mainfrom
Conversation
…nsparency and governance Signed-off-by: Tejas Saubhage <tsaubhage0007@gmail.com>
Signed-off-by: Tejas Saubhage <tsaubhage0007@gmail.com>
Added type: ignore[untyped-decorator] for tenacity @Retry decorator which lacks type stubs, resolving strict mypy check failure. Related to microsoft#720
|
Is that the only typing error if you turn on strict typing? I'm not sure if you meant to fix more or want me to review now. |
- Remove unused type: ignore comment in net_utility.py - Cast blob_stream.readall() to bytes in storage_io.py - Cast blob_properties.size > 0 to bool in storage_io.py python -m mypy pyrit/common/ --strict -> Success: no issues found in 20 source files python -m mypy pyrit/models/ --strict -> Success: no issues found in 26 source files
|
Hi @romanlutz, I have fixed all strict mypy errors in both common and models modules. |
- pyrit/auth/azure_auth.py: cast token_provider() to str, add type: ignore[no-any-return] for get_bearer_token_provider - pyrit/embedding/openai_text_embedding.py: remove unused type: ignore comment - pyrit/score/printer/console_scorer_printer.py: remove 6 unused type: ignore comments - pyrit/prompt_target/openai/openai_tts_target.py: remove 5 unused type: ignore comments - pyrit/prompt_target/openai/openai_completion_target.py: remove unused type: ignore comment - pyrit/prompt_target/hugging_face/hugging_face_chat_target.py: remove 2 unused type: ignore comments python -m mypy pyrit/ --strict -> Success: no issues found in 422 source files
|
Hi @romanlutz, I have now gone further and fixed all strict mypy errors across the entire pyrit codebase, not just common and models. |
|
If it's fixed across the entire codebase you may want to set the strict setting to true in the toml file |
- Enable strict = true in pyproject.toml (per reviewer romanlutz suggestion) - Fix 170 mypy strict errors across 61 files - Key patterns used: - Optional[T] annotations where = None defaults existed - assert x is not None guards before attribute access - or '' / or [] / or 0 fallbacks where semantically safe - cast() for typed dict .pop() returns - type: ignore[arg-type] inside lambdas where _try_register guards None - Added _client property on OpenAITarget for non-optional client access - Added memory property on PromptNormalizer for non-optional memory access
|
Hi @romanlutz, all review comments have been addressed: |
There was a problem hiding this comment.
Pull request overview
This PR tightens type-safety across PyRIT by enabling global strict mypy checks and applying a broad set of typing-related fixes (optionals, casts, and targeted ignores) across core runtime components (memory, targets, scorers, datasets, UI RPC, and backend routes).
Changes:
- Enable
mypystrict mode inpyproject.tomland adjust code to satisfy strict typing across the repository. - Add runtime/type-narrowing guards (assertions) and fix various Optional-related issues (e.g.,
Optional[...]annotations, casts, safer MIME/extension handling). - Refactor OpenAI target call sites to use a unified
_clientaccessor and remove some no-longer-needed type ignores.
Reviewed changes
Copilot reviewed 66 out of 66 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| pyproject.toml | Turns on global strict mypy settings. |
| pyrit/common/net_utility.py | Tightens httpx client kwargs typing/casts; related to Tenacity decorator typing. |
| pyrit/common/display_response.py | Normalizes formatting and adds storage IO guards for notebook image display. |
| pyrit/common/data_url_converter.py | Handles missing file extensions safely. |
| pyrit/ui/rpc.py | Adds asserts and adjusts wait_for_score to return Optional[Score]. |
| pyrit/ui/rpc_client.py | Adds asserts for RPC client internal fields. |
| pyrit/prompt_target/rpc_client.py | Adds asserts for RPC client internal fields. |
| pyrit/prompt_target/text_target.py | Changes CSV import default sequencing behavior for MessagePiece. |
| pyrit/prompt_target/prompt_shield_target.py | Adds required-value asserts and defaults API version. |
| pyrit/prompt_target/websocket_copilot_target.py | Hardens MIME type validation when checking image paths. |
| pyrit/prompt_target/openai/openai_target.py | Adds _client accessor and request_piece asserts; updates typing on helpers. |
| pyrit/prompt_target/openai/openai_chat_target.py | Uses _client for chat completions. |
| pyrit/prompt_target/openai/openai_completion_target.py | Uses _client for completions. |
| pyrit/prompt_target/openai/openai_image_target.py | Uses _client for image generation/edit. |
| pyrit/prompt_target/openai/openai_response_target.py | Uses _client for Responses API calls. |
| pyrit/prompt_target/openai/openai_tts_target.py | Uses _client for TTS calls and removes arg-type ignores. |
| pyrit/prompt_target/openai/openai_video_target.py | Uses _client for video calls; adds assertion for missing text piece. |
| pyrit/prompt_target/hugging_face/hugging_face_chat_target.py | Removes some ignores, guards Optional model_id usage, and tightens typing. |
| pyrit/prompt_normalizer/prompt_normalizer.py | Introduces memory property and replaces direct _memory access; adds return-value ignore. |
| pyrit/prompt_normalizer/normalizer_request.py | Makes converter configuration lists Optional. |
| pyrit/prompt_converter/template_segment_converter.py | Makes template parameters handling robust to Optional. |
| pyrit/prompt_converter/denylist_converter.py | Makes denylist optional in signature. |
| pyrit/prompt_converter/codechameleon_converter.py | Improves Optional check for encrypt_function. |
| pyrit/prompt_converter/add_text_image_converter.py | Defaults MIME type when detection fails. |
| pyrit/prompt_converter/add_image_text_converter.py | Defaults MIME type when detection fails. |
| pyrit/prompt_converter/azure_speech_text_to_audio_converter.py | Avoids returning None for audio path. |
| pyrit/score/scorer.py | Fixes Optional typing on registry behavior and adds targeted assignment ignores. |
| pyrit/score/printer/console_scorer_printer.py | Removes no-any-return ignores around Fore.* returns. |
| pyrit/score/human/human_in_the_loop_gradio.py | Asserts score presence after RPC call. |
| pyrit/score/true_false/self_ask_true_false_scorer.py | Adds assertion after loading YAML. |
| pyrit/score/true_false/prompt_shield_scorer.py | Ensures boolean parsing returns list[bool]. |
| pyrit/score/float_scale/self_ask_likert_scorer.py | Adds additional Likert scale enum entries. |
| pyrit/score/float_scale/azure_content_filter_scorer.py | Makes update_registry_behavior Optional in annotation. |
| pyrit/setup/initializers/components/scorers.py | Adds arg-type ignores and adjusts registration typing/formatting. |
| pyrit/setup/initializers/airt.py | Avoids passing Optional model names by defaulting to empty string. |
| pyrit/scenario/scenarios/foundry/red_team_agent.py | Avoids passing Optional endpoint into auth helper; adds ignore for kwargs typing. |
| pyrit/scenario/scenarios/airt/scam.py | Uses fallback empty list for seed_groups typing. |
| pyrit/scenario/scenarios/airt/psychosocial.py | Uses fallback empty list for seed_groups typing. |
| pyrit/scenario/scenarios/airt/jailbreak.py | Makes jailbreak_names Optional and uses fallback empty list for seed_groups. |
| pyrit/scenario/scenarios/airt/leakage.py | Avoids passing Optional endpoint into auth helper. |
| pyrit/scenario/scenarios/airt/cyber.py | Avoids passing Optional endpoint into auth helper. |
| pyrit/scenario/scenarios/airt/content_harms.py | Avoids passing Optional endpoint into auth helper. |
| pyrit/models/data_type_serializer.py | Makes internal fields Optional and changes storage IO selection/guards. |
| pyrit/models/message_piece.py | Adds type ignore when setting id = None. |
| pyrit/models/storage_io.py | Ensures Azure blob read returns bytes and normalizes boolean return. |
| pyrit/models/seeds/seed_prompt.py | Adds ignore on data_type assignment typing. |
| pyrit/models/seeds/seed_dataset.py | Normalizes optional path fields to strings when constructing simulated conversation seeds. |
| pyrit/memory/memory_interface.py | Makes key attributes Optional; adjusts serialization and schema reflection behavior. |
| pyrit/memory/memory_models.py | Adds targeted ignores and normalizes sequence defaulting. |
| pyrit/memory/central_memory.py | Makes singleton memory instance Optional. |
| pyrit/memory/sqlite_memory.py | Adds engine initialization checks before table operations. |
| pyrit/memory/azure_sql_memory.py | Adds engine/token guards and optional-expiry handling. |
| pyrit/backend/routes/media.py | Uses Optional-safe results_path when establishing allowed media root. |
| pyrit/backend/routes/version.py | Makes engine access optional-safe, but introduces a redundant conditional. |
| pyrit/cli/frontend_core.py | Tightens typing for dummy termcolor.cprint. |
| pyrit/auth/azure_auth.py | Normalizes token return types and adds ignore for provider callable typing. |
| pyrit/embedding/openai_text_embedding.py | Removes an unnecessary arg-type ignore for AsyncOpenAI init. |
| pyrit/executor/workflow/xpia.py | Adds asserts for callback and memory initialization. |
| pyrit/executor/promptgen/fuzzer/fuzzer.py | Asserts next_message is present before building requests. |
| pyrit/executor/attack/printer/markdown_printer.py | Refactors strategy identifier handling for Optional safety. |
| pyrit/executor/attack/multi_turn/tree_of_attacks.py | Removes a cast for metadata access and adds a scorer assert. |
| pyrit/analytics/result_analysis.py | Refactors strategy identifier handling for Optional safety. |
| pyrit/show_versions.py | Tightens deps_info typing and variable naming. |
| pyrit/datasets/seed_datasets/remote/vlsu_multimodal_dataset.py | Skips incomplete examples; hardens category fields; adjusts cache path building/guards. |
| pyrit/datasets/seed_datasets/remote/harmbench_multimodal_dataset.py | Adjusts cache path building/guards for image caching. |
Comments suppressed due to low confidence (4)
pyrit/prompt_target/text_target.py:83
- When
sequenceis missing from the CSV, this now forcessequence=0.MessagePiecealready has a default (sequence=-1), so using 0 changes ordering semantics (unknown vs first message). Consider omitting the argument when absent or defaulting to-1to preserve the model's default meaning.
pyrit/setup/initializers/components/scorers.py:157 - This
_try_registercall is split across lines in a way that hurts readability and makes the# type: ignorehard to associate with the correct expression. Consider formatting the call with one argument per line (or parentheses) and, instead of# type: ignore[arg-type], narrowgpt4oafter the non-None check (e.g., viacast/assert) so the scorer constructors receive a non-optionalPromptChatTarget.
pyrit/backend/routes/media.py:96 allowed_root = Path(memory.results_path or "").resolve()makes the traversal guard ineffective ifresults_pathis unset (empty string resolves to the current working directory). For safety, treat missing/emptyresults_pathas an initialization error and return 500 instead of defaulting to"".
try:
memory = CentralMemory.get_memory_instance()
allowed_root = Path(memory.results_path or "").resolve()
except Exception as exc:
raise HTTPException(status_code=500, detail="Memory not initialized; cannot determine results path.") from exc
# Path traversal guard
if not requested.is_relative_to(allowed_root):
raise HTTPException(status_code=403, detail="Access denied: path is outside the allowed results directory.")
pyrit/prompt_target/openai/openai_target.py:503
- There are two identical asserts for
request_piecein theContentFilterFinishReasonErrorhandler. This is dead duplication and should be reduced to a single assert (or reuse therequest_piecevariable created just above).
You can also share your feedback on Copilot code review. Take the survey.
- Replace duplicate assert statements with RuntimeError raises - Use DB_DATA_PATH as safe fallback instead of empty string - Validate results_path and results_storage_io before use - Simplify redundant nested conditional in version.py
|
Hi @romanlutz, addressed all Copilot review comments:
mypy still passes: python -m mypy pyrit/ --strict → no issues found in 422 source files Also need to investigate the pre-commit failure on macOS -will check that next. |
- copilot_authenticator.py: assert username/password not None before page.fill, remove unused type: ignore - _banner.py: rename role variable to char_role to fix type narrowing conflict - audio_transcript_scorer.py: cast bool expression to bool for no-any-return - azure_blob_storage_target.py: assert client not None before get_blob_client - xpia.py: assert response not None before get_value - context_compliance.py: assert response not None before get_value - tree_of_attacks.py: assert response not None before get_piece/get_value - prompt_normalizer.py: change return type to Optional[Message] python -m mypy pyrit/ --strict -> Success: no issues found in 425 source files
|
Hi @romanlutz, I have now fixed all strict mypy errors across the entire pyrit codebase including the cascading errors from the Optional[Message] return type change. |
|
Good call. I used assert as the quickest way to narrow Optional types for mypy, but you're right that they're unsafe in production since python -O strips them. I'll replace them with proper if x is None: raise ValueError(...) checks throughout. These are all invariant guards that shouldn't trigger in practice, but explicit raises are the right pattern here. Will push shortly |
|
Fixed — replaced all 67 assert x is not None guards with explicit if x is None: raise ValueError(...) checks so they're not stripped under python -O. mypy still passes: no issues found in 425 source files. |
# Conflicts: # pyrit/backend/routes/media.py # pyrit/datasets/seed_datasets/remote/vlsu_multimodal_dataset.py # pyrit/memory/memory_interface.py # pyrit/prompt_target/prompt_shield_target.py # pyrit/scenario/scenarios/airt/content_harms.py # pyrit/scenario/scenarios/airt/jailbreak.py # pyrit/scenario/scenarios/airt/psychosocial.py # pyrit/scenario/scenarios/airt/scam.py # pyrit/scenario/scenarios/foundry/red_team_agent.py # pyrit/setup/initializers/components/scorers.py
…nseException instead of returning None The return type was changed to Optional[Message] but every caller either immediately raised on None or would crash. Instead, raise EmptyResponseException at the source and keep the strong -> Message contract. Removes 8 redundant None guards across callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove duplicate null checks (openai_target.py, sqlite_memory.py) - Fix silent token refresh skip in azure_sql_memory.py (raise RuntimeError) - Replace Optional[X] with X | None per style guide (14 files) - Fix broken import placement in central_memory.py - Fix media.py or-empty path traversal risk (raise on missing results_path) - Fix DiskStorageIO fallback for Azure URLs (raise instead of silent fallback) - Standardize ValueError -> RuntimeError for 'not initialized' guards - Fix null check ordering in tree_of_attacks.py - Fix print_schema silent no-op (raise on missing engine) - Add missing exception docs (DOC501) across 19 files - Add 'from e' to raises in except blocks (B904) - Fix E501 line-too-long in harmbench and prompt_shield_scorer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Type narrowing with None guards and asserts for nullable values - Proper Optional typing (X | None) for variables assigned None - str() casts for colorama Any returns in console_scorer_printer - type: ignore for third-party SDK overload issues (OpenAI, HuggingFace) - Removed unused type: ignore comment in azure_auth.py - All 435 source files now pass mypy --strict with 0 errors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses reviewer feedback to avoid assert statements for type narrowing in production code, since asserts are stripped under python -O. Replaced 15 assert statements across 6 files with proper if/raise RuntimeError guards, and added corresponding DOC501 Raises sections to docstrings. Files changed: auth.py, pyrit_shell.py, component_identifier.py, evaluation_identifier.py, storage_io.py, azure_content_filter_scorer.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use ensure_async_token_provider to properly handle callable token providers instead of silently dropping them to None. Matches the pattern used in openai_target.py. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make _create_container_client_async return AsyncContainerClient so callers can assign directly, removing unreachable RuntimeError guards that only existed for mypy type narrowing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TextTarget.send_prompt_async returns [] (no response expected). Previously this would raise EmptyResponseException. Now empty lists are treated as valid write-only responses, returning the request as-is. EmptyResponseException is still raised for None or falsy entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The property was added for mypy type narrowing but never used anywhere. Removing dead code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Restore _client property on OpenAITarget (used by all subclasses) - Update normalizer test to expect EmptyResponseException for None responses - Update embedding test for ensure_async_token_provider wrapping Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The transformers library lacks type stubs in CI, causing no-untyped-call errors. Add a per-module mypy override to disable disallow_untyped_calls for the hugging_face module instead of fragile inline ignores. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Defensive type-narrowing guards (if x is None: raise) added for strict mypy cannot be reached in normal execution. Mark them with pragma to exclude from diff coverage calculation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove all pragma no cover comments from type-narrowing guards and add unit tests that verify each guard raises the expected error when the attribute is None. Tests cover guards across memory, models, identifiers, prompt targets, normalizer, converter, scorers, executors, scenarios, and setup modules. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Related to #720
The
@retrydecorator fromtenacitylacks type stubs, causing mypyto report an
untyped-decoratorerror when running strict mode:pyrit/common/net_utility.py:85: error: Untyped decorator makes function
"make_request_and_raise_if_error_async" untyped [untyped-decorator]
Added
# type: ignore[untyped-decorator]comment to suppress the erroruntil tenacity ships proper type stubs.
@romanlutz — tagging you as you filed the original issue.
Tests and Documentation
No new tests or documentation required for this change.
Verified fix by running:
python -m mypy pyrit/common/ --strict
Result: Success: no issues found in 20 source files