sarj-python-lint 1.44.0__tar.gz → 1.45.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 (213) hide show
  1. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/_file_context.py +1 -1
  4. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rule_base.py +1 -1
  5. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_annotation_semantics.py +1 -1
  6. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_comments.py +4 -1
  7. sarj_python_lint-1.45.0/src/sarj_python_lint/rules/_inherited_contract.py +7 -0
  8. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_paths.py +1 -1
  9. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_registry.py +2 -0
  10. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +174 -22
  11. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +1 -1
  12. sarj_python_lint-1.45.0/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +388 -0
  13. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +4 -0
  14. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +5 -1
  15. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +1 -1
  16. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +96 -1
  17. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +119 -28
  18. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/.gitignore +0 -0
  19. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/LICENSE +0 -0
  20. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/README.md +0 -0
  21. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/__init__.py +0 -0
  22. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/__main__.py +0 -0
  23. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  24. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/_filesystem.py +0 -0
  25. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/_python_target.py +0 -0
  26. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  27. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/_secret_names.py +0 -0
  28. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/_source.py +0 -0
  29. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/_version.py +0 -0
  30. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/benchmark.py +0 -0
  31. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  32. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/json_boundary.py +0 -0
  33. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/py.typed +0 -0
  34. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/ratchet.py +0 -0
  35. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  36. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  37. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  38. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  39. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  40. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  41. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  42. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  43. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  44. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  45. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  46. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  47. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  48. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  49. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  50. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  51. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  52. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  53. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  54. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  55. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  56. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  57. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  58. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  59. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  60. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  61. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  62. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  63. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  64. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  65. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  66. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  67. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  68. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  69. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  70. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  71. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  72. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  73. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  74. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  75. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  76. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  77. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  78. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  79. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  80. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  81. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  82. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  83. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  84. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  85. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  86. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  87. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  88. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  89. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  90. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  91. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  92. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  93. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  94. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  95. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  96. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  97. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  98. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  99. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  100. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  101. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  102. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  103. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  104. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  105. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  106. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  107. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  108. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  109. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  110. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  111. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  112. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  113. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  114. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  115. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  116. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  117. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  118. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  119. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  120. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  121. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  122. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  123. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  124. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  125. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  126. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  127. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  128. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  129. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  130. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  131. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  132. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  133. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  134. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  135. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  136. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  137. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  138. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  139. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  140. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  141. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  142. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  143. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  144. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  145. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  146. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  147. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  148. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  149. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  150. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  151. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  152. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  153. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  154. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  155. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  156. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  157. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  158. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  159. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  160. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  161. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  162. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  163. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  164. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  165. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  166. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  167. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  168. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  169. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  170. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  171. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  172. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  173. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  174. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  175. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  176. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  177. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  178. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  179. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  180. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  181. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  182. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  183. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  184. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  185. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  186. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  187. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  188. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  189. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  190. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  191. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  192. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  193. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  194. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  195. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  196. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  197. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  198. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  199. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  200. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  201. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  202. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  203. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  204. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  205. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  206. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  207. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  208. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  209. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  210. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  211. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  212. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  213. {sarj_python_lint-1.44.0 → sarj_python_lint-1.45.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.44.0
3
+ Version: 1.45.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.44.0"
3
+ version = "1.45.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" }]
@@ -40,7 +40,7 @@ class PythonFileContext:
40
40
  return unit.tree
41
41
  try:
42
42
  return ast.parse(self.source, filename=str(self.path))
43
- except SyntaxError:
43
+ except SyntaxError, UnicodeError:
44
44
  return None
45
45
 
46
46
  @cached_property
@@ -167,6 +167,6 @@ class ProjectRule(Rule, ABC):
167
167
  def parse_or_none(path: Path, source: str) -> ast.Module | None:
168
168
  try:
169
169
  tree = ast.parse(source, filename=str(path))
170
- except SyntaxError:
170
+ except SyntaxError, UnicodeError:
171
171
  tree = None
172
172
  return tree
@@ -52,7 +52,7 @@ class AnnotationSemantics:
52
52
  return annotation
53
53
  try:
54
54
  parsed = ast.parse(annotation.value.strip(), mode="eval").body
55
- except SyntaxError:
55
+ except SyntaxError, UnicodeError:
56
56
  return None
57
57
  ast.copy_location(parsed, annotation)
58
58
  return parsed
@@ -338,7 +338,10 @@ def _scan_memo(source: str) -> _Scan:
338
338
  global _last_scan # ruff: ignore[global-statement] — single-slot memo; the CLI runs rules per file sequentially
339
339
  if _last_scan is not None and _last_scan[0] is source:
340
340
  return _last_scan[1]
341
- result = _scan(source)
341
+ try:
342
+ result = _scan(source)
343
+ except UnicodeError:
344
+ result = _Scan([], [], set(), 1 << 30, [])
342
345
  _last_scan = (source, result)
343
346
  return result
344
347
 
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+
5
+
6
+ def inherited_contract(owner: ast.AST | None) -> bool:
7
+ return isinstance(owner, ast.ClassDef) and bool(owner.bases)
@@ -81,7 +81,7 @@ def _leading_header_bodies(source: str) -> list[str]:
81
81
  token = next(tokens)
82
82
  except StopIteration:
83
83
  break
84
- except IndentationError, tokenize.TokenError:
84
+ except IndentationError, tokenize.TokenError, UnicodeError:
85
85
  break
86
86
  if token.start[0] > _GENERATED_HEADER_LINES:
87
87
  break
@@ -218,6 +218,7 @@ from sarj_python_lint.rules.no_contradictory_type_guard import NoContradictoryTy
218
218
  from sarj_python_lint.rules.no_impossible_pydantic_field_bounds import NoImpossiblePydanticFieldBounds
219
219
  from sarj_python_lint.rules.no_invalid_pydantic_bypass import NoInvalidPydanticBypass
220
220
  from sarj_python_lint.rules.no_partial_type_is import NoPartialTypeIs
221
+ from sarj_python_lint.rules.no_shallow_record_type_guard import NoShallowRecordTypeGuard
221
222
  from sarj_python_lint.rules.no_unknown_pydantic_validator_field import NoUnknownPydanticValidatorField
222
223
  from sarj_python_lint.rules.prefer_discriminated_model_union import PreferDiscriminatedModelUnion
223
224
  from sarj_python_lint.rules.redundant_test_constructor_forwarder import RedundantTestConstructorForwarder
@@ -232,6 +233,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
232
233
  NoInvalidPydanticBypass.id: NoInvalidPydanticBypass,
233
234
  NoPartialTypeIs.id: NoPartialTypeIs,
234
235
  NoContradictoryTypeGuard.id: NoContradictoryTypeGuard,
236
+ NoShallowRecordTypeGuard.id: NoShallowRecordTypeGuard,
235
237
  NoConflictingPydanticAliasValidation.id: NoConflictingPydanticAliasValidation,
236
238
  NoUnknownPydanticValidatorField.id: NoUnknownPydanticValidatorField,
237
239
  NoImpossiblePydanticFieldBounds.id: NoImpossiblePydanticFieldBounds,
@@ -4,8 +4,9 @@ import ast
4
4
  from dataclasses import dataclass
5
5
  from itertools import starmap
6
6
  from pathlib import PurePosixPath
7
- from typing import TYPE_CHECKING, final, override
7
+ from typing import TYPE_CHECKING, Final, TypeIs, final, override
8
8
 
9
+ from sarj_python_lint.json_boundary import is_object_mapping
9
10
  from sarj_python_lint.rule_base import (
10
11
  AutofixPolicy,
11
12
  Diagnostic,
@@ -40,6 +41,9 @@ _PASSIVE_CONFIG = frozenset({"frozen", "strict", "validate_default"})
40
41
  _MODEL_APIS = frozenset({"model_construct", "model_validate", "model_validate_json", "model_validate_strings"})
41
42
  _MAX_ANNOTATION_DEPTH = 64
42
43
  _MAX_ANNOTATION_NODES = 1024
44
+ _MAX_LITERAL_TEXT = 65536
45
+ _UNKNOWN_LITERAL: Final = object()
46
+ _PAIR_ARITY = 2
43
47
 
44
48
 
45
49
  @dataclass(frozen=True, slots=True)
@@ -75,11 +79,12 @@ class NoInvalidPydanticBypass(Rule):
75
79
  autofix=AutofixPolicy.NONE,
76
80
  limitations=(
77
81
  "Only plain, direct, module-level Pydantic BaseModel subclasses with supported built-in and typing field annotations are inspected; hooks, inheritance, class decorators and custom methods are excluded.",
78
- "Only explicit None conflicts and closed string Literal domains are checked. Numeric Literal equality, bounds, opaque aliases, custom field types and coercions are not inferred.",
82
+ "Checks explicit None conflicts, closed string Literal domains, and bounded side-effect-free literal values contradicting builtin primitive or container field contracts. Coercible input is not assumed to be validated stored output.",
83
+ "Container literals preserve their actual construction semantics, including last-wins dictionary keys. Unpacking, calls, unhashable literal containers, opaque values, numeric Literal domains, bounds, aliases and custom field types are not inferred. Bool remains an int subtype, and numeric int-to-float/complex assignability is preserved.",
79
84
  "model_construct must use explicit keywords; omitted required fields are checked only when no unpacking or positional argument can supply them.",
80
85
  "model_copy requires a fresh exact constructor or a stable unescaped local constructor binding, plus a plain literal update dictionary. Arbitrary annotated receivers and overridden methods are excluded.",
81
86
  "Field aliases, non-passive model configuration, class or instance mutation and escape, ambiguous namespaces, tests and generated files are excluded.",
82
- "Direct model field annotations exceeding a bounded depth or node budget exclude the file rather than risking recursive analysis failure.",
87
+ "Direct model field annotations exceeding a bounded depth or node budget exclude the file rather than risking recursive analysis failure. Literal analysis also bounds the value tree and combined string/bytes content to avoid excessive witness work.",
83
88
  "Trusted valid construction and copy operations remain legal. No autofix chooses domain values or changes validation behavior.",
84
89
  ),
85
90
  examples=(
@@ -111,6 +116,36 @@ class NoInvalidPydanticBypass(Rule):
111
116
  expected_count=0,
112
117
  public=True,
113
118
  ),
119
+ RuleExample(
120
+ example_id="construct-invalid-list-member",
121
+ scenario="stored-literal-contract",
122
+ title="Unchecked list elements must satisfy the stored field type",
123
+ outcome=ExampleOutcome.MATCH,
124
+ files=(
125
+ ExampleFile.python(
126
+ "app/models.py",
127
+ "from pydantic import BaseModel\nclass Payload(BaseModel):\n labels: list[str]\npayload = Payload.model_construct(labels=[3])\n",
128
+ ),
129
+ ),
130
+ focus_path=PurePosixPath("app/models.py"),
131
+ expected_count=1,
132
+ public=True,
133
+ ),
134
+ RuleExample(
135
+ example_id="construct-trusted-list-members",
136
+ scenario="stored-literal-contract",
137
+ title="Trusted list elements preserve the field contract",
138
+ outcome=ExampleOutcome.NO_MATCH,
139
+ files=(
140
+ ExampleFile.python(
141
+ "app/models.py",
142
+ "from pydantic import BaseModel\nclass Payload(BaseModel):\n labels: list[str]\npayload = Payload.model_construct(labels=['ready'])\n",
143
+ ),
144
+ ),
145
+ focus_path=PurePosixPath("app/models.py"),
146
+ expected_count=0,
147
+ public=True,
148
+ ),
114
149
  ),
115
150
  )
116
151
  description = documentation.summary
@@ -161,7 +196,10 @@ def _bounded_annotation(annotation: ast.expr) -> bool:
161
196
  visited += 1
162
197
  if depth > _MAX_ANNOTATION_DEPTH or visited > _MAX_ANNOTATION_NODES:
163
198
  return False
164
- pending.extend((child, depth + 1) for child in ast.iter_child_nodes(node))
199
+ for child in ast.iter_child_nodes(node):
200
+ if visited + len(pending) >= _MAX_ANNOTATION_NODES:
201
+ return False
202
+ pending.append((child, depth + 1))
165
203
  return True
166
204
 
167
205
 
@@ -423,28 +461,142 @@ def _invalid_values(
423
461
  )
424
462
  for name, value in values.items():
425
463
  field = model.fields.get(name)
426
- if field is None or not isinstance(value, ast.Constant):
464
+ if field is None:
427
465
  continue
428
- domain = _string_domain(field.annotation, context)
429
- if domain is not None and value.value not in domain:
430
- findings.append(
431
- _diagnostic(
432
- value,
433
- context,
434
- f"Unchecked `{name}={value.value!r}` is outside `{model.node.name}`'s declared string Literal domain; validate a complete input mapping or supply an admitted value.",
435
- )
436
- )
437
- elif value.value is None and _excludes_none(field.annotation, context):
438
- findings.append(
439
- _diagnostic(
440
- value,
441
- context,
442
- f"Unchecked `{name}=None` contradicts `{model.node.name}`'s non-null field annotation; validate a complete input mapping or supply a non-null value.",
443
- )
444
- )
466
+ finding = _invalid_field_value(model, name, field.annotation, value, context)
467
+ if finding is not None:
468
+ findings.append(finding)
445
469
  return findings
446
470
 
447
471
 
472
+ def _invalid_field_value(
473
+ model: _Model, name: str, annotation: ast.expr, value: ast.expr, context: PythonFileContext
474
+ ) -> Diagnostic | None:
475
+ literal = _literal_value(value)
476
+ if literal is _UNKNOWN_LITERAL:
477
+ return None
478
+ if not _bounded_literal_text(annotation, value):
479
+ return None
480
+ domain = _string_domain(annotation, context)
481
+ if domain is not None and not any(literal == member for member in domain):
482
+ message = f"Unchecked `{name}` is outside `{model.node.name}`'s declared string Literal domain; validate a complete input mapping or supply an admitted value."
483
+ elif literal is None and _excludes_none(annotation, context):
484
+ message = f"Unchecked `{name}=None` contradicts `{model.node.name}`'s non-null field annotation; validate a complete input mapping or supply a non-null value."
485
+ elif _literal_incompatible(annotation, literal, context):
486
+ message = f"Unchecked `{name}` stores a literal incompatible with `{model.node.name}`'s field type; validate a complete input mapping or supply a value satisfying the stored contract."
487
+ else:
488
+ return None
489
+ return _diagnostic(value, context, message)
490
+
491
+
492
+ def _bounded_literal_text(annotation: ast.expr, value: ast.expr) -> bool:
493
+ length = 0
494
+ for expression in (annotation, value):
495
+ for node in ast.walk(expression):
496
+ if isinstance(node, ast.Constant) and isinstance(node.value, str | bytes):
497
+ length += len(node.value)
498
+ if length > _MAX_LITERAL_TEXT:
499
+ return False
500
+ return True
501
+
502
+
503
+ def _literal_value(node: ast.expr) -> object:
504
+ if not _bounded_annotation(node) or any(isinstance(member, ast.Call) for member in ast.walk(node)):
505
+ return _UNKNOWN_LITERAL
506
+ try:
507
+ return ast.literal_eval(node) # pyright: ignore[reportAny] -- bounded stdlib literal parser boundary exports object.
508
+ except ValueError, TypeError, SyntaxError, RecursionError:
509
+ return _UNKNOWN_LITERAL
510
+
511
+
512
+ def _literal_incompatible(annotation: ast.expr, value: object, context: PythonFileContext) -> bool:
513
+ if isinstance(annotation, ast.Constant):
514
+ return annotation.value is None and value is not None
515
+ if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
516
+ return _literal_incompatible(annotation.left, value, context) and _literal_incompatible(
517
+ annotation.right, value, context
518
+ )
519
+ if isinstance(annotation, ast.Name) and annotation.id in _BUILTINS:
520
+ return _builtin_incompatible(annotation.id, value)
521
+ if not isinstance(annotation, ast.Subscript):
522
+ return False
523
+ return _subscript_incompatible(annotation, value, context)
524
+
525
+
526
+ def _subscript_incompatible(annotation: ast.Subscript, value: object, context: PythonFileContext) -> bool:
527
+ members = _members(annotation.slice)
528
+ if _typing_wrapper(annotation, "Annotated", context):
529
+ return bool(members) and _literal_incompatible(members[0], value, context)
530
+ if _typing_wrapper(annotation, "Optional", context):
531
+ return value is not None and _literal_incompatible(annotation.slice, value, context)
532
+ if _typing_wrapper(annotation, "Union", context):
533
+ return bool(members) and all(_literal_incompatible(member, value, context) for member in members)
534
+ domain = _string_domain(annotation, context)
535
+ if domain is not None:
536
+ return not any(value == member for member in domain)
537
+ if isinstance(annotation.value, ast.Name) and annotation.value.id in _GENERICS:
538
+ return _container_incompatible(annotation.value.id, members, value, context)
539
+ return False
540
+
541
+
542
+ def _builtin_incompatible(kind: str, value: object) -> bool:
543
+ match kind:
544
+ case "str":
545
+ return not isinstance(value, str)
546
+ case "bytes":
547
+ return not isinstance(value, bytes)
548
+ case "bool":
549
+ return not isinstance(value, bool)
550
+ case "int":
551
+ return not isinstance(value, int)
552
+ case "float":
553
+ return not isinstance(value, int | float)
554
+ case "complex":
555
+ return not isinstance(value, int | float | complex)
556
+ case "list":
557
+ return not isinstance(value, list)
558
+ case "dict":
559
+ return not isinstance(value, dict)
560
+ case "set":
561
+ return not isinstance(value, set)
562
+ case "frozenset":
563
+ return not isinstance(value, frozenset)
564
+ case "tuple":
565
+ return not isinstance(value, tuple)
566
+ case _:
567
+ return False
568
+
569
+
570
+ def _container_incompatible(kind: str, members: list[ast.expr], value: object, context: PythonFileContext) -> bool:
571
+ if _builtin_incompatible(kind, value):
572
+ return True
573
+ if kind == "dict" and is_object_mapping(value) and len(members) == _PAIR_ARITY:
574
+ return any(
575
+ _literal_incompatible(members[0], key, context) or _literal_incompatible(members[1], item, context)
576
+ for key, item in value.items()
577
+ )
578
+ if _is_literal_items(value):
579
+ if kind == "tuple" and isinstance(value, tuple):
580
+ return _tuple_incompatible(members, value, context)
581
+ if kind in {"list", "set", "frozenset"} and len(members) == 1:
582
+ return any(_literal_incompatible(members[0], item, context) for item in value)
583
+ return False
584
+
585
+
586
+ def _is_literal_items(value: object) -> TypeIs[list[object] | set[object] | frozenset[object] | tuple[object, ...]]:
587
+ return isinstance(value, list | set | frozenset | tuple)
588
+
589
+
590
+ def _tuple_incompatible(members: list[ast.expr], value: tuple[object, ...], context: PythonFileContext) -> bool:
591
+ if len(members) == _PAIR_ARITY and _ellipsis(members[1]):
592
+ return any(_literal_incompatible(members[0], item, context) for item in value)
593
+ if any(_ellipsis(member) for member in members):
594
+ return False
595
+ return len(members) != len(value) or any(
596
+ _literal_incompatible(member, item, context) for member, item in zip(members, value, strict=False)
597
+ )
598
+
599
+
448
600
  def _string_domain(annotation: ast.expr, context: PythonFileContext) -> frozenset[str | None] | None:
449
601
  if isinstance(annotation, ast.Constant) and annotation.value is None:
450
602
  return frozenset({None})
@@ -848,6 +848,6 @@ def _module_type_aliases(tree: ast.Module, counts: dict[str, int]) -> dict[str,
848
848
  def _string_annotation_kind(value: str, types: _TypeIndex, seen: frozenset[str]) -> str | None:
849
849
  try:
850
850
  parsed = ast.parse(value, mode="eval")
851
- except SyntaxError:
851
+ except SyntaxError, UnicodeError:
852
852
  return None
853
853
  return _annotation_kind(parsed.body, types, seen)