From d2e5d604e10fb15ab380692ce46344223b2bcbc0 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:28:44 +0000 Subject: [PATCH] chore: add vulture dead-code detection and remove unreachable code Add vulture as the Python analogue of the frontend's knip: a make target (lint-deadcode), [tool.vulture] config, and a committed allowlist baseline (.vulture_allowlist.py) that ratchets like the existing ruff/basedpyright budgets. lint-deadcode-update regenerates the baseline. Remove the unreachable statements vulture surfaced at 100% confidence: duplicate returns, dead branches after unconditional returns, a redundant trailing pass, and a dead post-call logging block in the sagemaker handler. All are provably unreachable, so behavior is unchanged. --- .vulture_allowlist.py | 182 ++++++++++++++++++ Makefile | 13 ++ litellm/caching/qdrant_semantic_cache.py | 2 - litellm/litellm_core_utils/litellm_logging.py | 1 - .../litellm_core_utils/streaming_handler.py | 1 - litellm/llms/sagemaker/completion/handler.py | 8 - .../model_management_endpoints.py | 2 - .../policy_endpoints/endpoints.py | 1 - .../spend_management_endpoints.py | 2 - litellm/router.py | 2 - litellm/router_utils/cooldown_handlers.py | 2 - pyproject.toml | 7 + scripts/vulture_allowlist_update.py | 60 ++++++ 13 files changed, 262 insertions(+), 21 deletions(-) create mode 100644 .vulture_allowlist.py create mode 100644 scripts/vulture_allowlist_update.py diff --git a/.vulture_allowlist.py b/.vulture_allowlist.py new file mode 100644 index 00000000000..5359b3c18a5 --- /dev/null +++ b/.vulture_allowlist.py @@ -0,0 +1,182 @@ +# Vulture allowlist: baseline of accepted dead-code findings. +# +# This is the Python analogue of the frontend's knip setup. `make lint-deadcode` +# runs vulture to surface unreachable code, dead branches, and unused symbols. +# Entries below are referenced so vulture treats them as used. +# +# Almost all of these are false positives that vulture cannot resolve statically: +# provider config classes assign their __init__ params via locals() + setattr, and +# many "unused variable" hits are method parameters required by a protocol/contract +# (e.g. __aexit__'s exc_val/exc_tb, abstract hook signatures, **kwargs forwarding). +# +# Regenerate after intentionally adding or removing such patterns with +# `make lint-deadcode-update`; everything below the marker is generated. +# === generated below; do not edit by hand === + +ssl_ca_certs # unused variable (litellm/_redis.py:129) +host_type # unused variable (litellm/caching/qdrant_semantic_cache.py:39) +logged_item # unused variable (litellm/integrations/custom_logger.py:354) +default_span # unused variable (litellm/integrations/opentelemetry.py:2841) +timeout_millis # unused variable (litellm/integrations/otel/plumbing/providers.py:90) +hugging_face # unused variable (litellm/litellm_core_utils/get_litellm_params.py:63) +replicate # unused variable (litellm/litellm_core_utils/get_litellm_params.py:64) +together_ai # unused variable (litellm/litellm_core_utils/get_litellm_params.py:65) +voice_name # unused variable (litellm/llms/azure/text_to_speech/transformation.py:199) +exc_tb # unused variable (litellm/llms/base.py:74) +exc_val # unused variable (litellm/llms/base.py:74) +create_file_request # unused variable (litellm/llms/base_llm/files/transformation.py:217) +maxTokens # unused variable (litellm/llms/bedrock/chat/converse_transformation.py:116) +stopSequences # unused variable (litellm/llms/bedrock/chat/converse_transformation.py:117) +topP # unused variable (litellm/llms/bedrock/chat/converse_transformation.py:119) +topK # unused variable (litellm/llms/bedrock/chat/converse_transformation.py:120) +search_queries_only # unused variable (litellm/llms/bedrock/chat/invoke_handler.py:112) +prompt_truncation # unused variable (litellm/llms/bedrock/chat/invoke_handler.py:118) +return_prompt # unused variable (litellm/llms/bedrock/chat/invoke_handler.py:122) +raw_prompting # unused variable (litellm/llms/bedrock/chat/invoke_handler.py:124) +maxTokens # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_ai21_transformation.py:41) +topP # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_ai21_transformation.py:43) +stopSequences # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_ai21_transformation.py:44) +frequencePenalty # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_ai21_transformation.py:45) +presencePenalty # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_ai21_transformation.py:46) +countPenalty # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_ai21_transformation.py:47) +return_likelihood # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_cohere_transformation.py:28) +maxTokenCount # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_llama_transformation.py:27) +topP # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_llama_transformation.py:29) +maxTokenCount # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_titan_transformation.py:31) +stopSequences # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_titan_transformation.py:32) +topP # unused variable (litellm/llms/bedrock/chat/invoke_transformations/amazon_titan_transformation.py:34) +max_tokens_to_sample # unused variable (litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude2_transformation.py:32) +min_tokens # unused variable (litellm/llms/codestral/completion/transformation.py:27) +random_seed # unused variable (litellm/llms/codestral/completion/transformation.py:29) +prompt_truncation # unused variable (litellm/llms/cohere/chat/transformation.py:95) +connectors # unused variable (litellm/llms/cohere/chat/transformation.py:96) +search_queries_only # unused variable (litellm/llms/cohere/chat/transformation.py:97) +prompt_truncation # unused variable (litellm/llms/cohere/chat/v2_transformation.py:81) +connectors # unused variable (litellm/llms/cohere/chat/v2_transformation.py:82) +search_queries_only # unused variable (litellm/llms/cohere/chat/v2_transformation.py:83) +aimage_variation # unused variable (litellm/llms/custom_httpx/aiohttp_handler.py:590) +maximum_tokens # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:116) +minimum_tokens # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:117) +sequence_penalty # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:124) +sequence_penalty_min_length # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:125) +repetition_penalties_include_prompt # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:126) +repetition_penalties_include_completion # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:127) +use_multiplicative_presence_penalty # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:128) +use_multiplicative_frequency_penalty # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:129) +use_multiplicative_sequence_penalty # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:130) +penalty_bias # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:131) +penalty_exceptions_include_stop_sequences # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:132) +log_probs # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:136) +raw_completion # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:139) +disable_optimizations # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:140) +completion_bias_inclusion # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:141) +completion_bias_exclusion # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:142) +completion_bias_inclusion_first_token_only # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:143) +completion_bias_exclusion_first_token_only # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:144) +contextual_control_threshold # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:145) +control_log_additive # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:146) +default_max_tokens_to_sample # unused variable (litellm/llms/deprecated_providers/aleph_alpha.py:194) +prompt_truncate_len # unused variable (litellm/llms/fireworks_ai/chat/transformation.py:116) +context_length_exceeded_behavior # unused variable (litellm/llms/fireworks_ai/chat/transformation.py:117) +repetition_penalty # unused variable (litellm/llms/gigachat/chat/transformation.py:76) +profanity_check # unused variable (litellm/llms/gigachat/chat/transformation.py:77) +retrieval_method # unused variable (litellm/llms/gradient_ai/chat/transformation.py:33) +kb_filters # unused variable (litellm/llms/gradient_ai/chat/transformation.py:39) +filter_kb_content_by_query_metadata # unused variable (litellm/llms/gradient_ai/chat/transformation.py:40) +instruction_override # unused variable (litellm/llms/gradient_ai/chat/transformation.py:41) +include_functions_info # unused variable (litellm/llms/gradient_ai/chat/transformation.py:42) +include_retrieval_info # unused variable (litellm/llms/gradient_ai/chat/transformation.py:43) +include_guardrails_info # unused variable (litellm/llms/gradient_ai/chat/transformation.py:44) +provide_citations # unused variable (litellm/llms/gradient_ai/chat/transformation.py:45) +decoder_input_details # unused variable (litellm/llms/huggingface/embedding/transformation.py:66) +max_new_tokens # unused variable (litellm/llms/huggingface/embedding/transformation.py:68) +repetition_penalty # unused variable (litellm/llms/huggingface/embedding/transformation.py:69) +return_full_text # unused variable (litellm/llms/huggingface/embedding/transformation.py:70) +top_n_tokens # unused variable (litellm/llms/huggingface/embedding/transformation.py:74) +truncate # unused variable (litellm/llms/huggingface/embedding/transformation.py:76) +typical_p # unused variable (litellm/llms/huggingface/embedding/transformation.py:77) +watermark # unused variable (litellm/llms/huggingface/embedding/transformation.py:78) +repeat_penalty # unused variable (litellm/llms/lemonade/chat/transformation.py:41) +random_seed # unused variable (litellm/llms/mistral/chat/transformation.py:84) +safe_prompt # unused variable (litellm/llms/mistral/chat/transformation.py:85) +default_max_tokens_to_sample # unused variable (litellm/llms/nlp_cloud/chat/handler.py:29) +length_no_input # unused variable (litellm/llms/nlp_cloud/chat/transformation.py:69) +end_sequence # unused variable (litellm/llms/nlp_cloud/chat/transformation.py:70) +remove_end_sequence # unused variable (litellm/llms/nlp_cloud/chat/transformation.py:71) +remove_input # unused variable (litellm/llms/nlp_cloud/chat/transformation.py:72) +bad_words # unused variable (litellm/llms/nlp_cloud/chat/transformation.py:73) +repetition_penalty # unused variable (litellm/llms/nlp_cloud/chat/transformation.py:77) +num_beams # unused variable (litellm/llms/nlp_cloud/chat/transformation.py:78) +num_return_sequences # unused variable (litellm/llms/nlp_cloud/chat/transformation.py:79) +truncate # unused variable (litellm/llms/nvidia_nim/embed.py:33) +enforce_content_headers # unused variable (litellm/llms/oci/common_utils.py:69) +mirostat # unused variable (litellm/llms/ollama/chat/transformation.py:109) +mirostat_eta # unused variable (litellm/llms/ollama/chat/transformation.py:110) +mirostat_tau # unused variable (litellm/llms/ollama/chat/transformation.py:111) +num_ctx # unused variable (litellm/llms/ollama/chat/transformation.py:112) +num_gqa # unused variable (litellm/llms/ollama/chat/transformation.py:113) +num_thread # unused variable (litellm/llms/ollama/chat/transformation.py:114) +repeat_last_n # unused variable (litellm/llms/ollama/chat/transformation.py:115) +repeat_penalty # unused variable (litellm/llms/ollama/chat/transformation.py:116) +tfs_z # unused variable (litellm/llms/ollama/chat/transformation.py:120) +num_predict # unused variable (litellm/llms/ollama/chat/transformation.py:121) +mirostat # unused variable (litellm/llms/ollama/completion/transformation.py:106) +mirostat_eta # unused variable (litellm/llms/ollama/completion/transformation.py:107) +mirostat_tau # unused variable (litellm/llms/ollama/completion/transformation.py:108) +num_ctx # unused variable (litellm/llms/ollama/completion/transformation.py:109) +num_gqa # unused variable (litellm/llms/ollama/completion/transformation.py:110) +num_gpu # unused variable (litellm/llms/ollama/completion/transformation.py:111) +num_thread # unused variable (litellm/llms/ollama/completion/transformation.py:112) +repeat_last_n # unused variable (litellm/llms/ollama/completion/transformation.py:113) +repeat_penalty # unused variable (litellm/llms/ollama/completion/transformation.py:114) +tfs_z # unused variable (litellm/llms/ollama/completion/transformation.py:118) +num_predict # unused variable (litellm/llms/ollama/completion/transformation.py:119) +default_max_tokens_to_sample # unused variable (litellm/llms/oobabooga/chat/oobabooga.py:27) +do_sample # unused variable (litellm/llms/petals/completion/transformation.py:55) +repetition_penalty # unused variable (litellm/llms/petals/completion/transformation.py:59) +decoder_input_details # unused variable (litellm/llms/predibase/chat/transformation.py:57) +max_new_tokens # unused variable (litellm/llms/predibase/chat/transformation.py:59) +repetition_penalty # unused variable (litellm/llms/predibase/chat/transformation.py:60) +return_full_text # unused variable (litellm/llms/predibase/chat/transformation.py:61) +truncate # unused variable (litellm/llms/predibase/chat/transformation.py:67) +typical_p # unused variable (litellm/llms/predibase/chat/transformation.py:68) +watermark # unused variable (litellm/llms/predibase/chat/transformation.py:69) +api_token # unused variable (litellm/llms/replicate/chat/handler.py:25) +api_token # unused variable (litellm/llms/replicate/chat/handler.py:73) +max_new_tokens # unused variable (litellm/llms/replicate/chat/transformation.py:68) +min_new_tokens # unused variable (litellm/llms/replicate/chat/transformation.py:69) +max_new_tokens # unused variable (litellm/llms/sagemaker/completion/transformation.py:48) +return_full_text # unused variable (litellm/llms/sagemaker/completion/transformation.py:52) +auto_truncate # unused variable (litellm/llms/vertex_ai/vertex_embeddings/transformation.py:38) +decoding_method # unused variable (litellm/llms/watsonx/completion/transformation.py:94) +max_new_tokens # unused variable (litellm/llms/watsonx/completion/transformation.py:96) +min_new_tokens # unused variable (litellm/llms/watsonx/completion/transformation.py:97) +length_penalty # unused variable (litellm/llms/watsonx/completion/transformation.py:98) +repetition_penalty # unused variable (litellm/llms/watsonx/completion/transformation.py:102) +truncate_input_tokens # unused variable (litellm/llms/watsonx/completion/transformation.py:103) +include_stop_sequences # unused variable (litellm/llms/watsonx/completion/transformation.py:104) +return_options # unused variable (litellm/llms/watsonx/completion/transformation.py:105) +random_seed # unused variable (litellm/llms/watsonx/completion/transformation.py:106) +caching # unused variable (litellm/main.py:6083) +caching # unused variable (litellm/main.py:6109) +caching # unused variable (litellm/main.py:6136) +text_type # unused variable (litellm/proxy/guardrails/guardrail_hooks/dynamoai/dynamoai.py:101) +current_master_key # unused variable (litellm/proxy/management_endpoints/key_management_endpoints.py:4163) +operation_context # unused variable (litellm/proxy/openai_files_endpoints/common_utils.py:268) +add_key # unused variable (litellm/proxy/proxy_cli.py:879) +test_async # unused variable (litellm/proxy/proxy_cli.py:896) +config_type # unused variable (litellm/proxy/proxy_server.py:15331) +preserve_fields # unused variable (litellm/proxy/utils.py:6548) +is_retry # unused variable (litellm/router.py:4388) +is_fallback # unused variable (litellm/router.py:4389) +is_retry # unused variable (litellm/router.py:4425) +is_fallback # unused variable (litellm/router.py:4426) +is_retry # unused variable (litellm/router.py:4532) +is_fallback # unused variable (litellm/router.py:4533) +imageConfig # unused variable (litellm/utils.py:3242) +# unsatisfiable 'if' condition (litellm/a2a_protocol/providers/base.py:61) +# redundant if-condition (litellm/llms/bedrock/chat/invoke_handler.py:185) +# unsatisfiable 'if' condition (litellm/proxy/common_request_processing.py:415) +max_tokens_to_sample # unused variable (litellm/llms/anthropic/completion/transformation.py:69) +max_new_tokens # unused variable (litellm/llms/petals/completion/transformation.py:52) +# redundant if-condition (litellm/proxy/litellm_pre_call_utils.py:1779) diff --git a/Makefile b/Makefile index 076eac0f4a7..f18e493fd55 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ info lint lint-dev format \ lint-basedpyright lint-basedpyright-budget-update \ lint-ruff-budget lint-ruff-budget-update lint-budget-update lint-gate \ + lint-deadcode lint-deadcode-update \ install-dev install-proxy-dev install-test-deps install-hooks \ install-helm-unittest check-circular-imports check-import-safety @@ -31,6 +32,8 @@ help: @echo " make lint-gate - Strict ruff gate in CI-parity mode (fetches staging, simulates the merge)" @echo " make lint-ruff-budget-update - Re-capture per-rule baselines in ruff-strict-budget.json (ratchet)" @echo " make lint-budget-update - Re-capture all ratchet budgets (ruff + basedpyright)" + @echo " make lint-deadcode - Find dead Python code (unreachable branches, unused symbols) with vulture" + @echo " make lint-deadcode-update - Re-capture the vulture allowlist baseline (.vulture_allowlist.py)" @echo " make check-circular-imports - Check for circular imports" @echo " make check-import-safety - Check import safety" @echo " make test - Run all tests" @@ -148,6 +151,16 @@ lint-ruff-budget-update: install-dev # Ratchet all budgets in one shot (ruff strict + basedpyright) lint-budget-update: lint-ruff-budget-update lint-basedpyright-budget-update +# Dead-code detection (the Python analogue of the frontend's knip). +# Surfaces unreachable code, dead branches, and unused symbols. Config and the +# accepted-findings baseline live in pyproject.toml [tool.vulture] and +# .vulture_allowlist.py. On-demand, not wired into `make lint`. +lint-deadcode: install-dev + $(UV_RUN) vulture + +lint-deadcode-update: install-dev + $(UV_RUN) python scripts/vulture_allowlist_update.py + check-circular-imports: install-dev cd litellm && $(UV_RUN) python ../tests/documentation_tests/test_circular_imports.py && cd .. diff --git a/litellm/caching/qdrant_semantic_cache.py b/litellm/caching/qdrant_semantic_cache.py index 68d3b8c20b3..e2c498898b5 100644 --- a/litellm/caching/qdrant_semantic_cache.py +++ b/litellm/caching/qdrant_semantic_cache.py @@ -378,7 +378,6 @@ class QdrantSemanticCache(BaseCache): else: # cache miss ! return None - pass async def async_set_cache(self, key, value, **kwargs): from litellm._uuid import uuid @@ -486,7 +485,6 @@ class QdrantSemanticCache(BaseCache): else: # cache miss ! return None - pass async def _collection_info(self): return self.collection_info diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index d750a509054..440f5f54c6b 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -4527,7 +4527,6 @@ def _init_custom_logger_compatible_class( f"[Non-Blocking Error] Error initializing custom logger: {e}" ) return None - return None def _maybe_construct_otel_v2( diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index e278483d689..a030b0af9e3 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -720,7 +720,6 @@ class CustomStreamWrapper: "prompt_tokens": parsed_response.get("input_token_count", 0), "completion_tokens": parsed_response.get("generated_token_count", 0), } - return {"text": "", "is_finished": False} except Exception as e: raise e diff --git a/litellm/llms/sagemaker/completion/handler.py b/litellm/llms/sagemaker/completion/handler.py index aa4663666c2..3c1fdf83f26 100644 --- a/litellm/llms/sagemaker/completion/handler.py +++ b/litellm/llms/sagemaker/completion/handler.py @@ -386,14 +386,6 @@ class SagemakerLLM(BaseAWSLLM): return completion_stream - # LOGGING - logging_obj.post_call( - input=[], - api_key="", - original_response="first stream response received", - additional_args={"complete_input_dict": data}, - ) - except httpx.HTTPStatusError as err: error_code = err.response.status_code raise SagemakerError(status_code=error_code, message=err.response.text) diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index def6e271635..9d401620b44 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -1115,8 +1115,6 @@ class ModelManagementAuthChecks: else: return True - return True - #### [BETA] - This is a beta endpoint, format might change based on user feedback. - https://github.com/BerriAI/litellm/issues/964 @router.post( diff --git a/litellm/proxy/management_endpoints/policy_endpoints/endpoints.py b/litellm/proxy/management_endpoints/policy_endpoints/endpoints.py index 57578d98b75..2af8b31a442 100644 --- a/litellm/proxy/management_endpoints/policy_endpoints/endpoints.py +++ b/litellm/proxy/management_endpoints/policy_endpoints/endpoints.py @@ -381,7 +381,6 @@ async def test_policies_and_guardrails( ) results.append(item) return ApplyPoliciesListResult(results=results) - raise ValueError("Either inputs or inputs_list must be provided") except Exception as e: raise handle_exception_on_proxy(e) diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 0ba77dcd2f0..e9ba555e1f0 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -2487,8 +2487,6 @@ async def view_spend_logs( ) return data - return None - except Exception as e: if isinstance(e, HTTPException): raise ProxyException( diff --git a/litellm/router.py b/litellm/router.py index 6e7b9689415..ab418112a22 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -4766,8 +4766,6 @@ class Router: ) response = await self.async_function_with_fallbacks(**kwargs) return response - - return response except Exception as e: asyncio.create_task( send_llm_exception_alert( diff --git a/litellm/router_utils/cooldown_handlers.py b/litellm/router_utils/cooldown_handlers.py index 81bfac2ad19..e6ef25a012c 100644 --- a/litellm/router_utils/cooldown_handlers.py +++ b/litellm/router_utils/cooldown_handlers.py @@ -254,8 +254,6 @@ def _should_cooldown_deployment( original_exception=original_exception, ) - return False - def _set_cooldown_deployments( litellm_router_instance: LitellmRouter, diff --git a/pyproject.toml b/pyproject.toml index 1cb39153e83..6fea2be626e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -184,6 +184,7 @@ dev = [ "pytest-timeout==2.4.0", "vcrpy==8.2.1", "pytest-recording==0.13.4", + "vulture==2.16", ] proxy-dev = [ "prisma==0.11.0", @@ -269,6 +270,12 @@ source-exclude = [ [tool.isort] profile = "black" +[tool.vulture] +paths = ["litellm", ".vulture_allowlist.py"] +exclude = ["litellm/types/", "litellm/proxy/_experimental/", "litellm/proxy/example_config_yaml/"] +min_confidence = 100 +sort_by_size = true + [tool.commitizen] version = "1.91.0" version_files = [ diff --git a/scripts/vulture_allowlist_update.py b/scripts/vulture_allowlist_update.py new file mode 100644 index 00000000000..5a3933d3c05 --- /dev/null +++ b/scripts/vulture_allowlist_update.py @@ -0,0 +1,60 @@ +"""Regenerate the vulture allowlist baseline (.vulture_allowlist.py). + +Mirrors the ruff/basedpyright budget-update ratchet: re-capture the set of +currently accepted dead-code findings so `make lint-deadcode` only surfaces new +ones. The hand-written header is preserved; everything below it is regenerated +from `vulture --make-whitelist`. +""" + +from __future__ import annotations + +import subprocess +import sys +import tomllib +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +ALLOWLIST = REPO_ROOT / ".vulture_allowlist.py" +MARKER = "# === generated below; do not edit by hand ===" + + +def _read_header() -> str: + text = ALLOWLIST.read_text() + end = text.find(MARKER) + if end == -1: + raise SystemExit(f"Could not find marker {MARKER!r} in {ALLOWLIST}") + return text[: end + len(MARKER)] + "\n\n" + + +def _vulture_config() -> tuple[tuple[str, ...], tuple[str, ...]]: + config = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text())["tool"][ + "vulture" + ] + paths = tuple(p for p in config["paths"] if p != ALLOWLIST.name) + exclude = tuple(config.get("exclude", ())) + return paths, exclude + + +def main() -> int: + header = _read_header() + paths, exclude = _vulture_config() + cmd = [ + "vulture", + *paths, + "--min-confidence", + "100", + "--make-whitelist", + ] + if exclude: + cmd += ["--exclude", ",".join(exclude)] + result = subprocess.run(cmd, cwd=REPO_ROOT, capture_output=True, text=True) + if result.returncode not in (0, 3): + sys.stderr.write(result.stderr) + return result.returncode + ALLOWLIST.write_text(header + result.stdout) + print(f"Updated {ALLOWLIST.relative_to(REPO_ROOT)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())