sarj-python-lint 1.101.0__tar.gz → 1.101.1__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 (314) hide show
  1. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_opaque_contracts.py +12 -5
  4. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +5 -2
  5. sarj_python_lint-1.101.1/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +378 -0
  6. sarj_python_lint-1.101.0/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -189
  7. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/.gitignore +0 -0
  8. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/LICENSE +0 -0
  9. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/README.md +0 -0
  10. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/__init__.py +0 -0
  11. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/__main__.py +0 -0
  12. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  13. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix.py +0 -0
  14. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  15. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  16. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_comments.py +0 -0
  17. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  18. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  19. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_docs.py +0 -0
  20. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_enums.py +0 -0
  21. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  22. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_inert.py +0 -0
  23. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  24. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  25. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_security.py +0 -0
  26. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  27. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_tests.py +0 -0
  28. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_file_context.py +0 -0
  29. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_filesystem.py +0 -0
  30. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_fixes.py +0 -0
  31. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_parallel.py +0 -0
  32. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_prepared_sources.py +0 -0
  33. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_python_target.py +0 -0
  34. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  35. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_rule_descriptors.py +0 -0
  36. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_secret_names.py +0 -0
  37. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_source.py +0 -0
  38. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_version.py +0 -0
  39. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/application_logging.py +0 -0
  40. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/autofix.py +0 -0
  41. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/benchmark.py +0 -0
  42. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/cast_access.py +0 -0
  43. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
  44. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/json_boundary.py +0 -0
  45. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/py.typed +0 -0
  46. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/ratchet.py +0 -0
  47. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rule_base.py +0 -0
  48. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rule_metadata.py +0 -0
  49. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  50. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  51. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  52. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  53. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  54. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  55. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  56. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
  57. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  58. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  59. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_comments.py +0 -0
  60. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  61. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  62. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  63. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  64. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  65. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  66. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_exports.py +0 -0
  67. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  68. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  69. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  70. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  71. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  72. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  73. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  74. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
  75. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  76. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  77. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  78. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  79. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  80. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  81. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  82. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  83. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_postgres.py +0 -0
  84. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  85. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  86. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  87. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  88. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_registry.py +0 -0
  89. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  90. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  91. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  92. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  93. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
  94. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  95. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  96. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  97. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  98. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  99. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  100. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  101. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_store_sql.py +0 -0
  102. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  103. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  104. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  105. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  106. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  107. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  108. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  109. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  110. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  111. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  112. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  113. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  114. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  115. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  116. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  117. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  118. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  119. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  120. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  121. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  122. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  123. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  124. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  125. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  126. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  127. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  128. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  129. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  130. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  131. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  132. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  133. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  134. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  135. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
  136. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  137. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  138. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  139. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  140. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  141. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  142. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  143. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  144. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  145. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  146. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  147. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
  148. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  149. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  150. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  151. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
  152. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  153. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  154. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  155. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  156. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  157. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  158. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  159. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  160. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  161. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  162. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  163. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  164. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  165. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  166. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
  167. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  168. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  169. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  170. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  171. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  172. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  173. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  174. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  175. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  176. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  177. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  178. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  179. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  180. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  181. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
  182. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  183. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  184. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  185. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  186. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  187. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  188. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  189. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  190. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  191. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
  192. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
  193. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  194. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  195. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  196. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
  197. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  198. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  199. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  200. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  201. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  202. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  203. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  204. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  205. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  206. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  207. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  208. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  209. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
  210. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  211. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  212. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  213. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  214. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  215. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  216. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  217. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  218. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  219. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  220. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  221. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  222. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  223. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  224. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
  225. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  226. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  227. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  228. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  229. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  230. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  231. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  232. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  233. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  234. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  235. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  236. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  237. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  238. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  239. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  240. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  241. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  242. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  243. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  244. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  245. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  246. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  247. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  248. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  249. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
  250. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  251. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  252. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  253. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  254. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  255. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
  256. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  257. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  258. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  259. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
  260. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  261. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  262. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  263. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  264. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  265. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
  266. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  267. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  268. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  269. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  270. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  271. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  272. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  273. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  274. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  275. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  276. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  277. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  278. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  279. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  280. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  281. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  282. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
  283. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  284. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  285. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  286. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  287. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  288. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  289. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  290. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  291. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  292. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  293. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  294. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  295. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  296. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  297. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  298. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -0
  299. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  300. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  301. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  302. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  303. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
  304. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  305. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
  306. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  307. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  308. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  309. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  310. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  311. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  312. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  313. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  314. {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/suppression_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.101.0
3
+ Version: 1.101.1
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.101.0"
3
+ version = "1.101.1"
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" }]
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
16
  from pathlib import Path
17
17
 
18
18
  from sarj_python_lint._file_context import PythonFileContext
19
+ from sarj_python_lint.rules._local_source import LocalSourceFacts
19
20
 
20
21
 
21
22
  type _Function = ast.FunctionDef | ast.AsyncFunctionDef
@@ -111,11 +112,13 @@ class OpaqueContractFacts:
111
112
  context: PythonFileContext,
112
113
  facts: MappingShapeFacts,
113
114
  *,
115
+ local_source: LocalSourceFacts,
114
116
  modules: dict[Path, OpaqueContractFacts] | None = None,
115
117
  budget: _ProofBudget | None = None,
116
118
  ) -> None:
117
119
  self.context = context
118
120
  self.facts = facts
121
+ self.local_source = local_source
119
122
  self.native = NativeObjectContracts(context, facts)
120
123
  self.guards: dict[str, _Refinement | None] = {}
121
124
  self.checked: dict[_Function, frozenset[ast.arg]] = {}
@@ -712,20 +715,24 @@ class OpaqueContractFacts:
712
715
 
713
716
  def _imported_source(self, qualified: str) -> tuple[OpaqueContractFacts, ast.FunctionDef] | None:
714
717
  module, _, name = qualified.rpartition(".")
715
- path = self.context.session.local_source.resolve_module(
716
- self.context.path, module, 0, facts=self.context.session.first_party
717
- )
718
+ path = self.local_source.resolve_module(self.context.path, module, 0, facts=self.context.session.first_party)
718
719
  if path is not None and (owner := self.modules.get(path.resolve())) is not None:
719
720
  return owner.export(name)
720
721
  if len(self.modules) >= _MAX_GUARDS:
721
722
  return None
722
- loaded = self.context.session.local_source.read_module(path) if path is not None else None
723
+ loaded = self.local_source.read_module(path) if path is not None else None
723
724
  if loaded is None:
724
725
  return None
725
726
  from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- source loading runs after AnalysisSession initialization.
726
727
 
727
728
  context = PythonFileContext(loaded.path, loaded.source, self.context.session)
728
- owner = OpaqueContractFacts(context, MappingShapeFacts(context), modules=self.modules, budget=self.budget)
729
+ owner = OpaqueContractFacts(
730
+ context,
731
+ MappingShapeFacts(context),
732
+ local_source=self.local_source,
733
+ modules=self.modules,
734
+ budget=self.budget,
735
+ )
729
736
  return owner.export(name)
730
737
 
731
738
  def export(self, name: str) -> tuple[OpaqueContractFacts, ast.FunctionDef] | None:
@@ -15,6 +15,7 @@ from sarj_python_lint.rule_base import (
15
15
  RuleExample,
16
16
  Severity,
17
17
  )
18
+ from sarj_python_lint.rules._local_source import LocalSourceFacts
18
19
  from sarj_python_lint.rules._native_object_contracts import builtin_object, builtin_symbol
19
20
  from sarj_python_lint.rules._opaque_contracts import REFINEMENT_TYPES, OpaqueContractFacts
20
21
  from sarj_python_lint.rules.require_pydantic_for_mapping_shape import MappingShapeFacts
@@ -48,7 +49,7 @@ class PreferPreciseObjectContract(Rule):
48
49
  "Safe fixes narrow only a bare builtin object return annotation to builtin str for a closed private nested synchronous helper returning one exact string literal, with every call used directly as an if test and all containing return contracts explicit. Existing inert/metadata/binding proofs must hold; public/domain/container/parameter/model contracts and other result uses remain review-only.",
49
50
  "Checks builtin object in function signatures and class field annotations, including supported containers, callables, optional unions and quoted annotations. Local opaque values, object() sentinels, Literal/Annotated metadata, framework hooks, proven override signatures, and registered raw Pydantic callbacks are excluded. Functions that directly validate an object parameter through a source-visible BaseModel, BaseSettings or RootModel model_validate call, positionally or with obj=, retain that raw boundary. JSON text inputs still require a text or byte contract. Aliases must resolve to their source declaration and their bodies use definition scope. Alias graphs are bounded and shared branches reuse their facts. Imported or shadowed application types named object are preserved. Genuine opaque public contracts can use an explained exact-code exception; narrowing outside the exact private truthiness-only subset remains manual.",
50
51
  "An import-proven TypeGuard or TypeIs retains bare object on its guarded positional parameter and unchecked object slots in its declared refinement result; other object parameters remain checked. Guard correctness belongs to the type-guard rules. Universal type[object] class contracts and the return of a canonical module-level PEP 562 __getattr__ hook retain their opaque runtime contracts.",
51
- "Ordinary raw validators retain object inputs tied to builtin isinstance, exact builtin type comparisons or an actual source-resolved TypeGuard/TypeIs through rejecting branches, actual type/refinement assertions, accepting terminal branches with a rejecting fallback, or conditional expressions. Stable projections from containers with unchecked value slots retain their raw root contract when those values are validated. Source-visible local and imported validators may delegate those inputs and raw results; precise result narrowing does not justify a bare object output. Related unchecked mapping projections and transforms retain unknown values; import-proven native parsers and JSONDecoder preserve raw outputs. Universal builtin and stdlib interoperation may retain raw inputs. Independent object parameters, domain constructors and unproven parser names remain checked. Source resolution and dataflow are bounded with cycle guards; rebinding, decorators and namespace mutation invalidate delegated proof.",
52
+ "Ordinary raw validators retain object inputs tied to builtin isinstance, exact builtin type comparisons or an actual source-resolved TypeGuard/TypeIs through rejecting branches, actual type/refinement assertions, accepting terminal branches with a rejecting fallback, or conditional expressions. Stable projections from containers with unchecked value slots retain their raw root contract when those values are validated. Source-visible local and imported validators may delegate those inputs and raw results; precise result narrowing does not justify a bare object output. Related unchecked mapping projections and transforms retain unknown values; import-proven native parsers and JSONDecoder preserve raw outputs. Universal builtin and stdlib interoperation may retain raw inputs. Independent object parameters, domain constructors and unproven parser names remain checked. Source resolution and dataflow use a bounded per-file proof cache, captured source inputs and cycle guards; rebinding, decorators and namespace mutation invalidate delegated proof.",
52
53
  "Native opaque contracts include canonical ast.Constant.value reads and captures, FunctionType invocations, identity-only inputs and sys.getrefcount. Actual exec/eval globals and locals retain unknown values only through bounded native dict construction and alias flow; unrelated dictionaries and source domain factories do not borrow that ownership. Stub property returns on an import-proven runtime_checkable Protocol retain unchecked incoming attributes. Generic callable results, ordinary Protocol methods, application properties and unused parameters remain checked.",
53
54
  "Raw input unions may include scalar or None branches; only genuine unchecked container branches seed raw projections. Mapping consumers that inspect only keys retain uninspected value slots. Projected aliases require simple statement dominance, and precise active guards permit typed consumption after validation. Native namespace output reads require a preceding direct function-scope execution and abstain after authored mutations. Benign builtins imports do not hide builtin object contracts; explicit object shadowing and source-visible object mutations remain outside scope.",
54
55
  "An import-proven Iterable of unchecked values retains its raw element contract when every input use is a direct synchronous iteration and every reachable element read directly enters source-proven Pydantic model_validate. Deferred closures, rebinding, other iterator/element uses, unknown model providers and JSON-text parsers do not establish that boundary.",
@@ -131,7 +132,9 @@ class PreferPreciseObjectContract(Rule):
131
132
  if facts.provenance.wildcard:
132
133
  return []
133
134
  findings: list[Diagnostic] = []
134
- opaque = OpaqueContractFacts(context, facts)
135
+ opaque = OpaqueContractFacts(
136
+ context, facts, local_source=LocalSourceFacts(source_inputs=context.session.source_inputs)
137
+ )
135
138
  for owner, annotation in _contracts(context):
136
139
  if not _has_object(annotation, context, facts, at=annotation):
137
140
  continue
@@ -0,0 +1,378 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass
5
+ from pathlib import PurePosixPath
6
+ from typing import TYPE_CHECKING, final, override
7
+
8
+ from sarj_python_lint.rule_base import (
9
+ AutofixPolicy,
10
+ Diagnostic,
11
+ ExampleFile,
12
+ ExampleOutcome,
13
+ Rule,
14
+ RuleCategory,
15
+ RuleDocumentation,
16
+ RuleExample,
17
+ Severity,
18
+ is_suppressed,
19
+ )
20
+ from sarj_python_lint.rules._local_source import LocalSourceFacts
21
+ from sarj_python_lint.rules.require_pydantic_for_mapping_shape import MappingShapeFacts
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from pathlib import Path
26
+
27
+ from sarj_python_lint._file_context import PythonFileContext
28
+
29
+ _UNTYPED = frozenset(
30
+ {
31
+ "typing.Any",
32
+ "typing_extensions.Any",
33
+ "typing.Dict",
34
+ "typing.Mapping",
35
+ "typing.MutableMapping",
36
+ "collections.abc.Mapping",
37
+ "collections.abc.MutableMapping",
38
+ }
39
+ )
40
+ _JSON_PARSERS = frozenset({"json.load", "json.loads"})
41
+ _MAX_DEPTH = 32
42
+ _BAD = "def service_name(state: dict[str, object]):\n return state.get('name')\n"
43
+ _GOOD = "from typing import TypedDict\nclass State(TypedDict):\n name: str\ndef service_name(state: State) -> str | None:\n return state.get('name')\n"
44
+
45
+
46
+ @final
47
+ class PreferTypedMappingGet(Rule):
48
+ id = "prefer-typed-mapping-get"
49
+ code = "SARJ535"
50
+ documentation = RuleDocumentation(
51
+ default_level=Severity.WARNING,
52
+ summary="Read named fields from typed records instead of schema-free mappings.",
53
+ rationale="A get call on an Any/object-valued mapping or unchecked decoded JSON does not declare the field's type or distinguish a misspelled field from missing data. A default can conceal that gap.",
54
+ remediation="Validate external data into a named Pydantic model before reading its attributes. For trusted internal records, declare a TypedDict or another precise record. Preserve legitimate get calls on typed mappings, optional TypedDict fields and validated normalization callbacks.",
55
+ category=RuleCategory.MAINTAINABILITY,
56
+ autofix=AutofixPolicy.NONE,
57
+ limitations=(
58
+ "Checks get calls on string mappings annotated with object, Any or JsonValue values, bare standard dict/Mapping annotations, explicit Any/object receivers and import-proven json.load/loads results. Source-visible undecorated local or imported function returns, stable aliases, relative imports, reexports, projections and chained gets retain raw provenance through bounded resolution. Unannotated helpers are followed when every return is proven schema-free; precise return annotations retain their contract.",
59
+ "Precise mappings and TypedDicts, arbitrary unannotated parameters, application methods named get, unresolved external return types, ambiguous assignments and custom JSON object hooks abstain. Imported helpers are parsed without executing them, using a bounded per-file resolver that shares captured source inputs; dynamic or mutated namespaces, decorated functions and recursive or unresolved returns abstain. This syntax analysis does not replace the type checker.",
60
+ "Registered Pydantic raw normalization callbacks retain their validation boundary. Generated/vendor sources and wildcard or dynamic namespaces are excluded. No automatic rewrite because validation, defaults, missing keys and error handling can change behavior.",
61
+ "Existing mapping-shape and external-boundary diagnostics take precedence at the same read when selected together; selecting this rule alone still reports it.",
62
+ "Chained reads with the same starting position produce one actionable warning for their raw receiver.",
63
+ ),
64
+ examples=(
65
+ RuleExample(
66
+ example_id="opaque-get",
67
+ title="The field has no declared type",
68
+ outcome=ExampleOutcome.MATCH,
69
+ files=(ExampleFile.python("app/state.py", _BAD),),
70
+ focus_path=PurePosixPath("app/state.py"),
71
+ expected_count=1,
72
+ public=True,
73
+ ),
74
+ RuleExample(
75
+ example_id="typed-get",
76
+ title="TypedDict retains the field contract",
77
+ outcome=ExampleOutcome.NO_MATCH,
78
+ files=(ExampleFile.python("app/state.py", _GOOD),),
79
+ focus_path=PurePosixPath("app/state.py"),
80
+ expected_count=0,
81
+ public=True,
82
+ ),
83
+ RuleExample(
84
+ example_id="unchecked-json",
85
+ scenario="decoded-json",
86
+ title="Decoded JSON still needs validation",
87
+ outcome=ExampleOutcome.MATCH,
88
+ files=(
89
+ ExampleFile.python(
90
+ "app/state.py",
91
+ "import json\ndef name(text: str):\n state = json.loads(text)\n return state.get('name')\n",
92
+ ),
93
+ ),
94
+ focus_path=PurePosixPath("app/state.py"),
95
+ expected_count=1,
96
+ public=True,
97
+ ),
98
+ RuleExample(
99
+ example_id="validated-json",
100
+ scenario="decoded-json",
101
+ title="Validate JSON before reading its fields",
102
+ outcome=ExampleOutcome.NO_MATCH,
103
+ files=(
104
+ ExampleFile.python(
105
+ "app/state.py",
106
+ "from pydantic import BaseModel\nclass State(BaseModel):\n name: str\ndef name(text: str) -> str:\n return State.model_validate_json(text).name\n",
107
+ ),
108
+ ),
109
+ focus_path=PurePosixPath("app/state.py"),
110
+ expected_count=0,
111
+ public=True,
112
+ ),
113
+ RuleExample(
114
+ example_id="imported-parser-contract",
115
+ scenario="imported-return",
116
+ title="Imported parsing helpers keep their raw mapping contract",
117
+ outcome=ExampleOutcome.MATCH,
118
+ files=(
119
+ ExampleFile.python("app/__init__.py", "# package\n"),
120
+ ExampleFile.python(
121
+ "app/parser.py",
122
+ "import json\ndef parse(text: str) -> dict[str, object]:\n return json.loads(text)\n",
123
+ ),
124
+ ExampleFile.python(
125
+ "app/state.py",
126
+ "from .parser import parse\ndef name(text: str):\n return parse(text).get('name')\n",
127
+ ),
128
+ ),
129
+ focus_path=PurePosixPath("app/state.py"),
130
+ expected_count=1,
131
+ public=True,
132
+ ),
133
+ RuleExample(
134
+ example_id="imported-validated-model",
135
+ scenario="imported-return",
136
+ title="An imported parser validates a named model",
137
+ outcome=ExampleOutcome.NO_MATCH,
138
+ files=(
139
+ ExampleFile.python("app/__init__.py", "# package\n"),
140
+ ExampleFile.python(
141
+ "app/parser.py",
142
+ "from pydantic import BaseModel\nclass State(BaseModel):\n labels: dict[str, str]\n"
143
+ "def parse(text: str) -> State:\n return State.model_validate_json(text)\n",
144
+ ),
145
+ ExampleFile.python(
146
+ "app/state.py",
147
+ "from .parser import parse\ndef name(text: str):\n return parse(text).labels.get('name')\n",
148
+ ),
149
+ ),
150
+ focus_path=PurePosixPath("app/state.py"),
151
+ expected_count=0,
152
+ public=True,
153
+ ),
154
+ ),
155
+ )
156
+ description = documentation.summary
157
+
158
+ @override
159
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
160
+ if context.tree is None or context.generated or "get" not in context.symbol_source:
161
+ return []
162
+ facts = MappingShapeFacts(context)
163
+ if facts.provenance.wildcard or facts.provenance.dynamic_namespace:
164
+ return []
165
+ contracts = _ReturnContracts(facts)
166
+ findings = [
167
+ Diagnostic(
168
+ context.path,
169
+ call.lineno,
170
+ call.col_offset + 1,
171
+ self.code,
172
+ "Validate this schema-free mapping into a named model or use a precise internal record before reading fields with get.",
173
+ severity=Severity.WARNING,
174
+ )
175
+ for call in context.nodes(ast.Call)
176
+ if isinstance(call.func, ast.Attribute)
177
+ and call.func.attr == "get"
178
+ and not facts.raw_boundary(call)
179
+ and _untyped_receiver(call.func.value, facts, contracts)
180
+ and not is_suppressed(context.source_lines, call.lineno, self.code)
181
+ ]
182
+ return list({(finding.line, finding.col): finding for finding in findings}.values())
183
+
184
+
185
+ @dataclass(frozen=True, slots=True)
186
+ class _FunctionReturn:
187
+ owner: MappingShapeFacts
188
+ definition: ast.FunctionDef | ast.AsyncFunctionDef
189
+
190
+
191
+ @dataclass(frozen=True, slots=True)
192
+ class _ImportLocation:
193
+ module: str
194
+ level: int
195
+ name: str
196
+
197
+
198
+ class _ReturnContracts:
199
+ def __init__(self, facts: MappingShapeFacts) -> None:
200
+ self.owners = {facts.context.path: facts}
201
+ # Return lookups must leave other rules enough budget to resolve their validators.
202
+ self.local_source = LocalSourceFacts(source_inputs=facts.context.session.source_inputs)
203
+
204
+ def function(
205
+ self, expression: ast.expr, facts: MappingShapeFacts, seen: frozenset[ast.AST]
206
+ ) -> _FunctionReturn | None:
207
+ if len(seen) >= _MAX_DEPTH or facts.provenance.wildcard or facts.provenance.dynamic_namespace:
208
+ return None
209
+ attributes: list[str] = []
210
+ root = expression
211
+ while isinstance(root, ast.Attribute):
212
+ attributes.append(root.attr)
213
+ root = root.value
214
+ if (
215
+ not isinstance(root, ast.Name)
216
+ or facts.shadowed(root.id, expression)
217
+ or root.id in facts.provenance.mutated | facts.provenance.external_bindings
218
+ ):
219
+ return None
220
+ binding = facts.provenance.binding(root.id, expression)
221
+ if binding is None or binding in seen:
222
+ return None
223
+ match binding:
224
+ case ast.FunctionDef() | ast.AsyncFunctionDef() if not attributes:
225
+ return _FunctionReturn(facts, binding) if not binding.decorator_list else None
226
+ case ast.Name() if not attributes:
227
+ value = facts.provenance.assignment_value(binding, expression)
228
+ return (
229
+ self.function(value, facts, seen | {binding})
230
+ if isinstance(value, ast.Name | ast.Attribute)
231
+ else None
232
+ )
233
+ case ast.alias():
234
+ return self.imported(binding, tuple(reversed(attributes)), facts, seen)
235
+ case _:
236
+ return None
237
+
238
+ def imported(
239
+ self, binding: ast.alias, attributes: tuple[str, ...], facts: MappingShapeFacts, seen: frozenset[ast.AST]
240
+ ) -> _FunctionReturn | None:
241
+ if len(seen) >= _MAX_DEPTH or binding in seen:
242
+ return None
243
+ location = _import_location(binding, attributes, facts)
244
+ if location is None:
245
+ return None
246
+ path = self.local_source.resolve_module(
247
+ facts.context.path, location.module, location.level, facts=facts.context.session.first_party
248
+ )
249
+ if path is None or _namespace_uncertain(facts, path):
250
+ return None
251
+ owner = self._owner(path, facts)
252
+ if (
253
+ owner is None
254
+ or owner.context.tree is None
255
+ or owner.provenance.wildcard
256
+ or owner.provenance.dynamic_namespace
257
+ ):
258
+ return None
259
+ bindings = owner.provenance.bindings.get(owner.context.tree, {}).get(location.name, ())
260
+ if len(bindings) != 1 or location.name in owner.provenance.mutated | owner.provenance.external_bindings:
261
+ return None
262
+ [target] = bindings
263
+ following = seen | {binding}
264
+ match target:
265
+ case ast.FunctionDef() | ast.AsyncFunctionDef():
266
+ result = _FunctionReturn(owner, target) if not target.decorator_list else None
267
+ case ast.alias():
268
+ result = self.imported(target, (), owner, following)
269
+ case _:
270
+ result = None
271
+ return result if result is not None and not _namespace_uncertain(facts, result.owner.context.path) else None
272
+
273
+ def _owner(self, path: Path, facts: MappingShapeFacts) -> MappingShapeFacts | None:
274
+ if path not in self.owners:
275
+ if len(self.owners) >= _MAX_DEPTH:
276
+ return None
277
+ module = self.local_source.read_module(path)
278
+ if module is None:
279
+ return None
280
+ from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- source owners are constructed after analysis-session initialization.
281
+
282
+ self.owners[path] = MappingShapeFacts(PythonFileContext(path, module.source, facts.context.session))
283
+ return self.owners[path]
284
+
285
+
286
+ def _import_location(
287
+ binding: ast.alias, attributes: tuple[str, ...], facts: MappingShapeFacts
288
+ ) -> _ImportLocation | None:
289
+ parent = facts.context.parents.get(binding)
290
+ match parent:
291
+ case ast.ImportFrom(module=module, level=level):
292
+ parts = (*((module,) if module else ()), *((binding.name, *attributes[:-1]) if attributes else ()))
293
+ return _ImportLocation(".".join(parts), level, attributes[-1] if attributes else binding.name)
294
+ case ast.Import() if attributes:
295
+ prefix = binding.name if binding.asname else binding.name.partition(".")[0]
296
+ return _ImportLocation(".".join((prefix, *attributes[:-1])), 0, attributes[-1])
297
+ case _:
298
+ return None
299
+
300
+
301
+ def _namespace_uncertain(facts: MappingShapeFacts, path: Path) -> bool:
302
+ if facts.provenance.mock_namespace_patching:
303
+ return True
304
+ target = path.resolve()
305
+ return any(
306
+ target == source or (source.name == "__init__.py" and target.is_relative_to(source.parent))
307
+ for source in facts.provenance.imported_namespace_hazards
308
+ )
309
+
310
+
311
+ def _untyped_receiver(
312
+ node: ast.expr, facts: MappingShapeFacts, contracts: _ReturnContracts, *, seen: frozenset[ast.AST] = frozenset()
313
+ ) -> bool:
314
+ if len(seen) >= _MAX_DEPTH:
315
+ return False
316
+ if facts.mapping(node):
317
+ return True
318
+ match node:
319
+ case ast.Name():
320
+ return _name_receiver(node, facts, contracts, seen)
321
+ case ast.Call(func=func, keywords=keywords) if facts.qualified(func, node) in _JSON_PARSERS:
322
+ return not any(keyword.arg in {None, "object_hook", "object_pairs_hook", "cls"} for keyword in keywords)
323
+ case ast.Call(func=ast.Attribute(value=receiver, attr="get")) | ast.Subscript(value=receiver):
324
+ return _untyped_receiver(receiver, facts, contracts, seen=seen | {node})
325
+ case ast.Await(value=ast.Call() as call):
326
+ return _returned_mapping(call, facts, contracts, seen, awaited=True)
327
+ case ast.Call():
328
+ return _returned_mapping(node, facts, contracts, seen, awaited=False)
329
+ case _:
330
+ return False
331
+
332
+
333
+ def _returned_mapping(
334
+ call: ast.Call, facts: MappingShapeFacts, contracts: _ReturnContracts, seen: frozenset[ast.AST], *, awaited: bool
335
+ ) -> bool:
336
+ resolved = contracts.function(call.func, facts, seen)
337
+ if resolved is None:
338
+ return False
339
+ owner, function = resolved.owner, resolved.definition
340
+ if function in seen or isinstance(function, ast.AsyncFunctionDef) != awaited:
341
+ return False
342
+ if function.returns is not None:
343
+ return owner.annotation(function.returns, function.returns, mapping=True) or _erased_annotation(
344
+ function.returns, owner
345
+ )
346
+ returned = [node for node in owner.context.nodes(ast.Return) if owner.provenance.scope(node) is function]
347
+ return bool(returned) and all(
348
+ node.value is not None and _untyped_receiver(node.value, owner, contracts, seen=seen | {function})
349
+ for node in returned
350
+ )
351
+
352
+
353
+ def _name_receiver(
354
+ node: ast.Name, facts: MappingShapeFacts, contracts: _ReturnContracts, seen: frozenset[ast.AST]
355
+ ) -> bool:
356
+ if node.id in facts.provenance.external_bindings:
357
+ return False
358
+ binding = facts.provenance.binding(node.id, node)
359
+ if binding is None or binding in seen:
360
+ return False
361
+ if isinstance(binding, ast.arg):
362
+ return binding.annotation is not None and _erased_annotation(binding.annotation, facts)
363
+ parent = facts.context.parents.get(binding)
364
+ if isinstance(parent, ast.AnnAssign):
365
+ return _erased_annotation(parent.annotation, facts)
366
+ value = facts.provenance.assignment_value(binding, node) if isinstance(binding, ast.Name) else None
367
+ return value is not None and _untyped_receiver(value, facts, contracts, seen=seen | {binding})
368
+
369
+
370
+ def _erased_annotation(node: ast.expr, facts: MappingShapeFacts) -> bool:
371
+ resolved = facts.resolve_annotation(node)
372
+ if resolved is None:
373
+ return False
374
+ return (
375
+ facts.builtin(resolved.expression, "dict", resolved.at)
376
+ or facts.builtin(resolved.expression, "object", resolved.at)
377
+ or facts.qualified(resolved.expression, resolved.at) in _UNTYPED
378
+ )
@@ -1,189 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import ast
4
- from pathlib import PurePosixPath
5
- from typing import TYPE_CHECKING, 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.require_pydantic_for_mapping_shape import MappingShapeFacts
20
-
21
-
22
- if TYPE_CHECKING:
23
- from sarj_python_lint._file_context import PythonFileContext
24
-
25
- _UNTYPED = frozenset(
26
- {
27
- "typing.Any",
28
- "typing_extensions.Any",
29
- "typing.Dict",
30
- "typing.Mapping",
31
- "typing.MutableMapping",
32
- "collections.abc.Mapping",
33
- "collections.abc.MutableMapping",
34
- }
35
- )
36
- _JSON_PARSERS = frozenset({"json.load", "json.loads"})
37
- _MAX_DEPTH = 32
38
- _BAD = "def service_name(state: dict[str, object]):\n return state.get('name')\n"
39
- _GOOD = "from typing import TypedDict\nclass State(TypedDict):\n name: str\ndef service_name(state: State) -> str | None:\n return state.get('name')\n"
40
-
41
-
42
- @final
43
- class PreferTypedMappingGet(Rule):
44
- id = "prefer-typed-mapping-get"
45
- code = "SARJ535"
46
- documentation = RuleDocumentation(
47
- default_level=Severity.WARNING,
48
- summary="Read named fields from typed records instead of schema-free mappings.",
49
- rationale="A get call on an Any/object-valued mapping or unchecked decoded JSON does not declare the field's type or distinguish a misspelled field from missing data. A default can conceal that gap.",
50
- remediation="Validate external data into a named Pydantic model before reading its attributes. For trusted internal records, declare a TypedDict or another precise record. Preserve legitimate get calls on typed mappings, optional TypedDict fields and validated normalization callbacks.",
51
- category=RuleCategory.MAINTAINABILITY,
52
- autofix=AutofixPolicy.NONE,
53
- limitations=(
54
- "Checks get calls on string mappings annotated with object, Any or JsonValue values, bare standard dict/Mapping annotations, explicit Any/object receivers and import-proven json.load/loads results. Source-visible undecorated function return annotations, stable local aliases, projections and chained gets retain raw provenance through bounded resolution.",
55
- "Precise mappings and TypedDicts, arbitrary unannotated parameters, application methods named get, unresolved external return types, ambiguous assignments and custom JSON object hooks abstain. This syntax analysis does not replace the type checker.",
56
- "Registered Pydantic raw normalization callbacks retain their validation boundary. Generated/vendor sources and wildcard or dynamic namespaces are excluded. No automatic rewrite because validation, defaults, missing keys and error handling can change behavior.",
57
- "Existing mapping-shape and external-boundary diagnostics take precedence at the same read when selected together; selecting this rule alone still reports it.",
58
- "Chained reads with the same starting position produce one actionable warning for their raw receiver.",
59
- ),
60
- examples=(
61
- RuleExample(
62
- example_id="opaque-get",
63
- title="The field has no declared type",
64
- outcome=ExampleOutcome.MATCH,
65
- files=(ExampleFile.python("app/state.py", _BAD),),
66
- focus_path=PurePosixPath("app/state.py"),
67
- expected_count=1,
68
- public=True,
69
- ),
70
- RuleExample(
71
- example_id="typed-get",
72
- title="TypedDict retains the field contract",
73
- outcome=ExampleOutcome.NO_MATCH,
74
- files=(ExampleFile.python("app/state.py", _GOOD),),
75
- focus_path=PurePosixPath("app/state.py"),
76
- expected_count=0,
77
- public=True,
78
- ),
79
- RuleExample(
80
- example_id="unchecked-json",
81
- scenario="decoded-json",
82
- title="Decoded JSON still needs validation",
83
- outcome=ExampleOutcome.MATCH,
84
- files=(
85
- ExampleFile.python(
86
- "app/state.py",
87
- "import json\ndef name(text: str):\n state = json.loads(text)\n return state.get('name')\n",
88
- ),
89
- ),
90
- focus_path=PurePosixPath("app/state.py"),
91
- expected_count=1,
92
- public=True,
93
- ),
94
- RuleExample(
95
- example_id="validated-json",
96
- scenario="decoded-json",
97
- title="Validate JSON before reading its fields",
98
- outcome=ExampleOutcome.NO_MATCH,
99
- files=(
100
- ExampleFile.python(
101
- "app/state.py",
102
- "from pydantic import BaseModel\nclass State(BaseModel):\n name: str\ndef name(text: str) -> str:\n return State.model_validate_json(text).name\n",
103
- ),
104
- ),
105
- focus_path=PurePosixPath("app/state.py"),
106
- expected_count=0,
107
- public=True,
108
- ),
109
- ),
110
- )
111
- description = documentation.summary
112
-
113
- @override
114
- def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
115
- if context.tree is None or context.generated or "get" not in context.symbol_source:
116
- return []
117
- facts = MappingShapeFacts(context)
118
- if facts.provenance.wildcard or facts.provenance.dynamic_namespace:
119
- return []
120
- findings = [
121
- Diagnostic(
122
- context.path,
123
- call.lineno,
124
- call.col_offset + 1,
125
- self.code,
126
- "Validate this schema-free mapping into a named model or use a precise internal record before reading fields with get.",
127
- severity=Severity.WARNING,
128
- )
129
- for call in context.nodes(ast.Call)
130
- if isinstance(call.func, ast.Attribute)
131
- and call.func.attr == "get"
132
- and not facts.raw_boundary(call)
133
- and _untyped_receiver(call.func.value, facts)
134
- and not is_suppressed(context.source_lines, call.lineno, self.code)
135
- ]
136
- return list({(finding.line, finding.col): finding for finding in findings}.values())
137
-
138
-
139
- def _untyped_receiver(node: ast.expr, facts: MappingShapeFacts, *, seen: frozenset[ast.AST] = frozenset()) -> bool:
140
- if len(seen) >= _MAX_DEPTH:
141
- return False
142
- if facts.mapping(node):
143
- return True
144
- match node:
145
- case ast.Name():
146
- return _name_receiver(node, facts, seen)
147
- case ast.Call(func=func, keywords=keywords) if facts.qualified(func, node) in _JSON_PARSERS:
148
- return not any(keyword.arg in {None, "object_hook", "object_pairs_hook", "cls"} for keyword in keywords)
149
- case ast.Call(func=ast.Attribute(value=receiver, attr="get")) | ast.Subscript(value=receiver):
150
- return _untyped_receiver(receiver, facts, seen=seen | {node})
151
- case ast.Call(func=ast.Name(id=name)):
152
- function = facts.provenance.binding(name, node)
153
- return (
154
- isinstance(function, ast.FunctionDef)
155
- and not function.decorator_list
156
- and function.returns is not None
157
- and (
158
- facts.annotation(function.returns, function.returns, mapping=True)
159
- or _erased_annotation(function.returns, facts)
160
- )
161
- )
162
- case _:
163
- return False
164
-
165
-
166
- def _name_receiver(node: ast.Name, facts: MappingShapeFacts, seen: frozenset[ast.AST]) -> bool:
167
- if node.id in facts.provenance.external_bindings:
168
- return False
169
- binding = facts.provenance.binding(node.id, node)
170
- if binding is None or binding in seen:
171
- return False
172
- if isinstance(binding, ast.arg):
173
- return binding.annotation is not None and _erased_annotation(binding.annotation, facts)
174
- parent = facts.context.parents.get(binding)
175
- if isinstance(parent, ast.AnnAssign):
176
- return _erased_annotation(parent.annotation, facts)
177
- value = facts.provenance.assignment_value(binding, node) if isinstance(binding, ast.Name) else None
178
- return value is not None and _untyped_receiver(value, facts, seen=seen | {binding})
179
-
180
-
181
- def _erased_annotation(node: ast.expr, facts: MappingShapeFacts) -> bool:
182
- resolved = facts.resolve_annotation(node)
183
- if resolved is None:
184
- return False
185
- return (
186
- facts.builtin(resolved.expression, "dict", resolved.at)
187
- or facts.builtin(resolved.expression, "object", resolved.at)
188
- or facts.qualified(resolved.expression, resolved.at) in _UNTYPED
189
- )