sarj-python-lint 1.42.2__tar.gz → 1.43.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_registry.py +6 -0
  4. sarj_python_lint-1.43.0/src/sarj_python_lint/rules/_type_guard_symbols.py +112 -0
  5. sarj_python_lint-1.43.0/src/sarj_python_lint/rules/no_contradictory_type_guard.py +198 -0
  6. sarj_python_lint-1.43.0/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +501 -0
  7. sarj_python_lint-1.43.0/src/sarj_python_lint/rules/no_partial_type_is.py +270 -0
  8. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +4 -78
  9. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +16 -4
  10. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/.gitignore +0 -0
  11. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/LICENSE +0 -0
  12. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/README.md +0 -0
  13. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/__init__.py +0 -0
  14. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/__main__.py +0 -0
  15. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  16. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_file_context.py +0 -0
  17. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_filesystem.py +0 -0
  18. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_python_target.py +0 -0
  19. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  20. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_secret_names.py +0 -0
  21. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_source.py +0 -0
  22. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_version.py +0 -0
  23. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/benchmark.py +0 -0
  24. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  25. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/json_boundary.py +0 -0
  26. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/py.typed +0 -0
  27. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/ratchet.py +0 -0
  28. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rule_base.py +0 -0
  29. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  30. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  31. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  32. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  33. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  34. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  35. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  36. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  37. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  38. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  39. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  40. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  41. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  42. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  43. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  44. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  45. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  46. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  47. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  48. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  49. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  50. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  51. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  52. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  53. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  54. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  55. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  56. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  57. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  58. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  59. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  60. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  61. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  62. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  63. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  64. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  65. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  66. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  67. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  68. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  69. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  70. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  71. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  72. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  73. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  74. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  75. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  76. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  77. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  78. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  79. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  80. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  81. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  82. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  83. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  84. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  85. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  86. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  87. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  88. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  89. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  90. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  91. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  92. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  93. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  94. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  95. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  96. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  97. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  98. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  99. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  100. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  101. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  102. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  103. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  104. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  105. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  106. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  107. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  108. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  109. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  110. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  111. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  112. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  113. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  114. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  115. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  116. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  117. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  118. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  119. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  120. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  121. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  122. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  123. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  124. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  125. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  126. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  127. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  128. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  129. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  130. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  131. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  132. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  133. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  134. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  135. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  136. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  137. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  138. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  139. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  140. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  141. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  142. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  143. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  144. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  145. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  146. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  147. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  148. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  149. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  150. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  151. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  152. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  153. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  154. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  155. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  156. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  157. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  158. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  159. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  160. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  161. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  162. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  163. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  164. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  165. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  166. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  167. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  168. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  169. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  170. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  171. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  172. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  173. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  174. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  175. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  176. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  177. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  178. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  179. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  180. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  181. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  182. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  183. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  184. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  185. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  186. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  187. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  188. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  189. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  190. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  191. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  192. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  193. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  194. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  195. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  196. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  197. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  198. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  199. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  200. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  201. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  202. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  203. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  204. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  205. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  206. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  207. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  208. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  209. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  210. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  211. {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.42.2
3
+ Version: 1.43.0
4
4
  Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
5
5
  Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
6
6
  Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "1.42.2"
3
+ version = "1.43.0"
4
4
  description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "sarj-ai" }]
@@ -214,7 +214,10 @@ if TYPE_CHECKING:
214
214
  from sarj_python_lint.rules.duplicate_adjacent_exception_handler import DuplicateAdjacentExceptionHandler
215
215
  from sarj_python_lint.rules.duplicate_closed_pure_helper import DuplicateClosedPureHelper
216
216
  from sarj_python_lint.rules.no_conflicting_pydantic_alias_validation import NoConflictingPydanticAliasValidation
217
+ from sarj_python_lint.rules.no_contradictory_type_guard import NoContradictoryTypeGuard
217
218
  from sarj_python_lint.rules.no_impossible_pydantic_field_bounds import NoImpossiblePydanticFieldBounds
219
+ from sarj_python_lint.rules.no_invalid_pydantic_bypass import NoInvalidPydanticBypass
220
+ from sarj_python_lint.rules.no_partial_type_is import NoPartialTypeIs
218
221
  from sarj_python_lint.rules.no_unknown_pydantic_validator_field import NoUnknownPydanticValidatorField
219
222
  from sarj_python_lint.rules.prefer_discriminated_model_union import PreferDiscriminatedModelUnion
220
223
  from sarj_python_lint.rules.redundant_test_constructor_forwarder import RedundantTestConstructorForwarder
@@ -226,6 +229,9 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
226
229
  {
227
230
  NoExplicitTestOracle.id: NoExplicitTestOracle,
228
231
  PreferDiscriminatedModelUnion.id: PreferDiscriminatedModelUnion,
232
+ NoInvalidPydanticBypass.id: NoInvalidPydanticBypass,
233
+ NoPartialTypeIs.id: NoPartialTypeIs,
234
+ NoContradictoryTypeGuard.id: NoContradictoryTypeGuard,
229
235
  NoConflictingPydanticAliasValidation.id: NoConflictingPydanticAliasValidation,
230
236
  NoUnknownPydanticValidatorField.id: NoUnknownPydanticValidatorField,
231
237
  NoImpossiblePydanticFieldBounds.id: NoImpossiblePydanticFieldBounds,
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from typing import TYPE_CHECKING
5
+
6
+ from sarj_python_lint.rules._imports import TYPING_SOURCES
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ from sarj_python_lint._file_context import PythonFileContext
11
+ from sarj_python_lint.rules._imports import ImportIndex
12
+
13
+
14
+ def builtin_guard_symbol(node: ast.expr | None, imports: ImportIndex, builtin_symbols: frozenset[str]) -> str | None:
15
+ if node is None:
16
+ return None
17
+ symbol = imports.resolved_symbol(node, sources=frozenset({"builtins"}))
18
+ if symbol in builtin_symbols:
19
+ return symbol
20
+ if isinstance(node, ast.Name) and node.id in builtin_symbols and imports.builtin_is_unshadowed(node.id):
21
+ return node.id
22
+ return None
23
+
24
+
25
+ def guard_symbols_ambiguous(context: PythonFileContext, builtin_symbols: frozenset[str]) -> bool:
26
+ if _namespace_escapes(context):
27
+ return True
28
+ mutation_targets = {f"builtins.{symbol}" for symbol in builtin_symbols} | {
29
+ f"{module}.{symbol}" for module in TYPING_SOURCES for symbol in ("TypeGuard", "TypeIs")
30
+ }
31
+ sensitive_names = builtin_symbols | {
32
+ name
33
+ for name, target in context.module_imports.bindings.items()
34
+ if target.module in TYPING_SOURCES | {"builtins"}
35
+ and (target.symbol is None or target.symbol in builtin_symbols | {"TypeGuard", "TypeIs"})
36
+ }
37
+ return any(
38
+ _ambiguous_node(node, context, mutation_targets, sensitive_names)
39
+ for node in context.nodes(
40
+ ast.Import,
41
+ ast.ImportFrom,
42
+ ast.Attribute,
43
+ ast.ExceptHandler,
44
+ ast.MatchAs,
45
+ ast.MatchStar,
46
+ ast.MatchMapping,
47
+ ast.NamedExpr,
48
+ )
49
+ )
50
+
51
+
52
+ def _namespace_escapes(context: PythonFileContext) -> bool:
53
+ if context.dynamic_namespace or any(node.id == "__builtins__" for node in context.nodes(ast.Name)):
54
+ return True
55
+ namespace_names = {
56
+ name
57
+ for name, target in context.module_imports.bindings.items()
58
+ if target.symbol is None and target.module in TYPING_SOURCES | {"builtins"}
59
+ }
60
+ for name in context.nodes(ast.Name):
61
+ if name.id not in namespace_names:
62
+ continue
63
+ parent = context.parents.get(name)
64
+ if not isinstance(parent, ast.Attribute) or parent.value is not name or parent.attr == "__dict__":
65
+ return True
66
+ return False
67
+
68
+
69
+ def _ambiguous_node(
70
+ node: ast.AST, context: PythonFileContext, mutation_targets: set[str], sensitive_names: frozenset[str]
71
+ ) -> bool:
72
+ match node:
73
+ case ast.Import() | ast.ImportFrom():
74
+ return _ambiguous_import(node, context.module_imports, sensitive_names)
75
+ case ast.Attribute(ctx=(ast.Store() | ast.Del())):
76
+ return context.module_imports.resolved_qualified_name(node) in mutation_targets
77
+ case (
78
+ ast.ExceptHandler(name=name)
79
+ | ast.MatchAs(name=name)
80
+ | ast.MatchStar(name=name)
81
+ | ast.MatchMapping(rest=name)
82
+ | ast.NamedExpr(target=ast.Name(id=name))
83
+ ):
84
+ return name in sensitive_names
85
+ case _:
86
+ return False
87
+
88
+
89
+ def _ambiguous_import(node: ast.Import | ast.ImportFrom, imports: ImportIndex, sensitive_names: frozenset[str]) -> bool:
90
+ if isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names):
91
+ return True
92
+ for alias in node.names:
93
+ name = alias.asname or alias.name.partition(".")[0]
94
+ if name not in sensitive_names:
95
+ continue
96
+ if not _preserves_canonical_import(node, alias, imports):
97
+ return True
98
+ return False
99
+
100
+
101
+ def _preserves_canonical_import(node: ast.Import | ast.ImportFrom, alias: ast.alias, imports: ImportIndex) -> bool:
102
+ if isinstance(node, ast.ImportFrom):
103
+ if node.level:
104
+ return False
105
+ module, symbol = node.module, alias.name
106
+ else:
107
+ module, symbol = alias.name, None
108
+ name = alias.asname or alias.name.partition(".")[0]
109
+ target = imports.bindings.get(name)
110
+ if target is None:
111
+ return module == "builtins" and symbol == name
112
+ return target.module == module and target.symbol == symbol
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import PurePosixPath
5
+ from typing import TYPE_CHECKING, ClassVar, Final, final, override
6
+
7
+ from sarj_python_lint.rule_base import (
8
+ AutofixPolicy,
9
+ Diagnostic,
10
+ ExampleFile,
11
+ ExampleOutcome,
12
+ Rule,
13
+ RuleCategory,
14
+ RuleDocumentation,
15
+ RuleExample,
16
+ Severity,
17
+ is_suppressed,
18
+ )
19
+ from sarj_python_lint.rules._imports import TYPING_SOURCES
20
+ from sarj_python_lint.rules._type_guard_symbols import builtin_guard_symbol, guard_symbols_ambiguous
21
+
22
+
23
+ if TYPE_CHECKING:
24
+ from sarj_python_lint._file_context import PythonFileContext
25
+ from sarj_python_lint.rules._imports import ImportIndex
26
+
27
+
28
+ _SCALARS = frozenset({"int", "bool", "str", "bytes"})
29
+ _BUILTINS = _SCALARS | {"object", "isinstance"}
30
+ _WITNESSES: Final = {
31
+ "int": frozenset({"int", "bool"}),
32
+ "bool": frozenset({"bool"}),
33
+ "str": frozenset({"str"}),
34
+ "bytes": frozenset({"bytes"}),
35
+ }
36
+ _ALL_WITNESSES = frozenset({"int", "bool", "str", "bytes"})
37
+ _MAX_INPUT_NODES = 4096
38
+ _BAD = (
39
+ "from typing import TypeGuard\ndef is_text(value: object) -> TypeGuard[str]:\n return isinstance(value, int)\n"
40
+ )
41
+ _GOOD = _BAD.replace("isinstance(value, int)", "isinstance(value, str)")
42
+
43
+
44
+ @final
45
+ class NoContradictoryTypeGuard(Rule):
46
+ id = "no-contradictory-type-guard"
47
+ code = "SARJ513"
48
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
49
+ default_level=Severity.WARNING,
50
+ summary="Primitive type guards must agree with the isinstance check they return.",
51
+ rationale=(
52
+ "A guard annotation does not validate its implementation. Returning an incompatible isinstance "
53
+ "check makes callers trust the wrong primitive; TypeIs also incorrectly excludes rejected target "
54
+ "values in the false branch."
55
+ ),
56
+ remediation=(
57
+ "Align the declared target and runtime test. For an intentional partial predicate, TypeGuard may "
58
+ "narrow only the true branch; TypeIs must recognize every target value admitted by its input. "
59
+ "Review callers before changing the narrowing contract."
60
+ ),
61
+ category=RuleCategory.CORRECTNESS,
62
+ autofix=AutofixPolicy.NONE,
63
+ limitations=(
64
+ "Checks undecorated, non-generic module functions with one parameter and a single returned direct builtin isinstance call, optionally after a docstring.",
65
+ "Guard symbols must resolve to typing or typing_extensions TypeGuard or TypeIs. Targets and checked classes are builtin int, bool, str or bytes; bool is treated as an int subtype.",
66
+ "Inputs must be object or fully classified plain unions of those primitive types and None. Impossible isinstance tests and compiler-invalid TypeIs target/input relationships are excluded.",
67
+ "Input unions are traversed iteratively with a bounded node budget; overly complex annotations are excluded.",
68
+ "Literal and refined targets, aliases, custom classes, float numeric promotion, tuple isinstance tests, helper predicates, nested functions and complex bodies are not inferred.",
69
+ "TypeGuard may intentionally recognize a subset of its target, so TypeGuard[int] with an isinstance bool test remains legal. Visible namespace mutation and ambiguous or shadowed symbols abstain conservatively.",
70
+ "Generated files are excluded; maintained tests remain checked. No autofix guesses a domain or changes false-branch behavior.",
71
+ ),
72
+ examples=(
73
+ RuleExample(
74
+ example_id="wrong-primitive-promise",
75
+ title="An integer test does not prove a string",
76
+ outcome=ExampleOutcome.MATCH,
77
+ files=(ExampleFile.python("app/guards.py", _BAD),),
78
+ focus_path=PurePosixPath("app/guards.py"),
79
+ expected_count=1,
80
+ public=True,
81
+ ),
82
+ RuleExample(
83
+ example_id="consistent-primitive-promise",
84
+ title="Recognize the primitive promised to callers",
85
+ outcome=ExampleOutcome.NO_MATCH,
86
+ files=(ExampleFile.python("app/guards.py", _GOOD),),
87
+ focus_path=PurePosixPath("app/guards.py"),
88
+ expected_count=0,
89
+ public=True,
90
+ ),
91
+ ),
92
+ )
93
+ description = documentation.summary
94
+
95
+ @override
96
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
97
+ if context.generated or not any(symbol in context.symbol_source for symbol in ("TypeGuard", "TypeIs")):
98
+ return []
99
+ if context.tree is None or guard_symbols_ambiguous(context, _BUILTINS):
100
+ return []
101
+ findings: list[Diagnostic] = []
102
+ for function in context.tree.body:
103
+ if not isinstance(function, ast.FunctionDef):
104
+ continue
105
+ statement = _contradictory_return(function, context)
106
+ if statement is None or is_suppressed(context.source_lines, statement.lineno, self.code):
107
+ continue
108
+ findings.append(
109
+ Diagnostic(
110
+ context.path,
111
+ statement.lineno,
112
+ statement.col_offset + 1,
113
+ self.code,
114
+ "This guard's isinstance test contradicts its primitive narrowing promise; align the target "
115
+ "and test, preserving TypeIs false-branch behavior and intentional TypeGuard partial predicates.",
116
+ severity=Severity.WARNING,
117
+ )
118
+ )
119
+ return findings
120
+
121
+
122
+ def _contradictory_return(function: ast.FunctionDef, context: PythonFileContext) -> ast.Return | None:
123
+ parameter = _sole_parameter(function)
124
+ annotation = function.returns
125
+ if parameter is None or not isinstance(annotation, ast.Subscript):
126
+ return None
127
+ guard = context.module_imports.resolved_symbol(annotation.value, sources=TYPING_SOURCES)
128
+ if guard not in {"TypeGuard", "TypeIs"}:
129
+ return None
130
+ target_kind = builtin_guard_symbol(annotation.slice, context.module_imports, _BUILTINS)
131
+ target = _WITNESSES.get(target_kind) if target_kind is not None else None
132
+ inputs = _input_witnesses(parameter.annotation, context.module_imports)
133
+ if target is None or inputs is None or (guard == "TypeIs" and not target <= inputs):
134
+ return None
135
+ statement = _single_return(function)
136
+ checked_kind = _checked_kind(statement.value, parameter.arg, context) if statement is not None else None
137
+ checked = _WITNESSES.get(checked_kind) if checked_kind is not None else None
138
+ if checked is None or not inputs & checked:
139
+ return None
140
+ accepts_wrong = bool((inputs & checked) - target)
141
+ rejects_target = guard == "TypeIs" and bool((inputs & target) - checked)
142
+ return statement if accepts_wrong or rejects_target else None
143
+
144
+
145
+ def _sole_parameter(function: ast.FunctionDef) -> ast.arg | None:
146
+ args = function.args
147
+ positional = [*args.posonlyargs, *args.args]
148
+ if function.decorator_list or function.type_params or len(positional) != 1:
149
+ return None
150
+ if args.kwonlyargs or args.vararg is not None or args.kwarg is not None:
151
+ return None
152
+ return positional[0]
153
+
154
+
155
+ def _single_return(function: ast.FunctionDef) -> ast.Return | None:
156
+ body = function.body
157
+ if (
158
+ body
159
+ and isinstance(body[0], ast.Expr)
160
+ and isinstance(body[0].value, ast.Constant)
161
+ and isinstance(body[0].value.value, str)
162
+ ):
163
+ body = body[1:]
164
+ return body[0] if len(body) == 1 and isinstance(body[0], ast.Return) else None
165
+
166
+
167
+ def _input_witnesses(annotation: ast.expr | None, imports: ImportIndex) -> frozenset[str] | None:
168
+ pending = [annotation]
169
+ visited = 0
170
+ witnesses: set[str] = set()
171
+ while pending:
172
+ member = pending.pop()
173
+ visited += 1
174
+ if visited > _MAX_INPUT_NODES:
175
+ return None
176
+ if isinstance(member, ast.Constant) and member.value is None:
177
+ continue
178
+ if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
179
+ pending.extend((member.left, member.right))
180
+ continue
181
+ kind = builtin_guard_symbol(member, imports, _BUILTINS)
182
+ if kind == "object":
183
+ witnesses.update(_ALL_WITNESSES)
184
+ elif kind in _WITNESSES:
185
+ witnesses.update(_WITNESSES[kind])
186
+ else:
187
+ return None
188
+ return frozenset(witnesses)
189
+
190
+
191
+ def _checked_kind(node: ast.expr | None, parameter: str, context: PythonFileContext) -> str | None:
192
+ match node:
193
+ case ast.Call(func=func, args=[ast.Name(id=name), target], keywords=[]) if name == parameter:
194
+ if builtin_guard_symbol(func, context.imports, _BUILTINS) == "isinstance":
195
+ return builtin_guard_symbol(target, context.imports, _BUILTINS)
196
+ case _:
197
+ pass
198
+ return None