sarj-python-lint 1.3.1__tar.gz → 1.3.3__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 (151) hide show
  1. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +2 -2
  4. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +109 -5
  5. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/.gitignore +0 -0
  6. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/LICENSE +0 -0
  7. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/README.md +0 -0
  8. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/__init__.py +0 -0
  9. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/__main__.py +0 -0
  10. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/_analysis_session.py +0 -0
  11. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/_file_context.py +0 -0
  12. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/_filesystem.py +0 -0
  13. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/_python_target.py +0 -0
  14. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  15. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/_secret_names.py +0 -0
  16. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/_version.py +0 -0
  17. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/benchmark.py +0 -0
  18. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/json_boundary.py +0 -0
  19. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/py.typed +0 -0
  20. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/ratchet.py +0 -0
  21. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rule_base.py +0 -0
  22. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/__init__.py +0 -0
  23. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  24. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  25. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  26. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_comments.py +0 -0
  27. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  28. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  29. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_first_party.py +0 -0
  30. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  31. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_imports.py +0 -0
  32. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_logging.py +0 -0
  33. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  34. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_paths.py +0 -0
  35. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_project_index.py +0 -0
  36. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  37. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_pytest.py +0 -0
  38. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_registry.py +0 -0
  39. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  40. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_sql.py +0 -0
  41. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  42. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  43. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  44. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  45. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  46. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  47. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  48. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  49. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  50. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  51. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  52. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  53. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  54. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  55. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  56. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  57. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  58. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  59. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  60. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  61. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  62. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  63. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  64. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  65. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  66. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  67. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  68. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  69. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  70. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  71. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  72. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  73. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  74. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  75. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  76. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  77. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  78. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  79. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  80. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  81. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  82. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  83. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  84. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  85. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  86. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  87. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  88. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  89. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  90. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  91. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  92. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  93. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  94. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  95. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  96. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  97. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  98. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  99. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  100. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  101. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  102. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  103. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  104. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  105. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  106. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  107. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  108. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  109. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  110. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  111. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  112. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  113. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  114. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  115. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  116. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  117. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  118. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  119. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  120. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  121. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  122. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  123. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  124. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  125. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  126. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  127. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  128. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  129. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  130. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  131. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  132. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  133. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  134. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  135. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  136. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  137. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  138. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  139. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  140. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  141. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  142. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  143. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/stepdown.py +0 -0
  144. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  145. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  146. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  147. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  148. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  149. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  150. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  151. {sarj_python_lint-1.3.1 → sarj_python_lint-1.3.3}/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.3.1
3
+ Version: 1.3.3
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.3.1"
3
+ version = "1.3.3"
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" }]
@@ -272,11 +272,11 @@ class _NameUsage(ast.NodeVisitor):
272
272
 
273
273
  @override
274
274
  def visit_Lambda(self, node: ast.Lambda) -> None:
275
- _ = node
275
+ """Skip the deferred lambda body when tracking immediate name usage."""
276
276
 
277
277
  @override
278
278
  def visit_GeneratorExp(self, node: ast.GeneratorExp) -> None:
279
- _ = node
279
+ """Skip the deferred generator body when tracking immediate name usage."""
280
280
 
281
281
  def visit_Import(self, node: ast.Import) -> None:
282
282
  self.rebound |= any((alias.asname or alias.name.split(".", maxsplit=1)[0]) == self.name for alias in node.names)
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import ast
4
4
  from collections import Counter
5
- from dataclasses import dataclass
5
+ from dataclasses import dataclass, replace
6
6
  from pathlib import PurePosixPath
7
7
  from typing import TYPE_CHECKING, ClassVar, NamedTuple, final, override
8
8
 
@@ -67,9 +67,16 @@ class _WrapperSummary:
67
67
  accessors: dict[str, str]
68
68
 
69
69
 
70
+ @dataclass(frozen=True, slots=True)
71
+ class _WrapperFactorySummary:
72
+ wrapper: _WrapperSummary
73
+ accessor_origins: dict[str, frozenset[ast.Call]]
74
+
75
+
70
76
  @dataclass(frozen=True, slots=True)
71
77
  class _ModuleSummaries:
72
78
  wrappers: dict[str, _WrapperSummary]
79
+ wrapper_factories: dict[str, _WrapperFactorySummary]
73
80
  decoder_parameters: dict[str, int]
74
81
  record_parameters: dict[str, frozenset[int]]
75
82
  local_parameters: dict[str, frozenset[int]]
@@ -122,7 +129,8 @@ class RequirePydanticForExternalJson(Rule):
122
129
  autofix=AutofixPolicy.NONE,
123
130
  limitations=(
124
131
  "The rule follows import-proven JSON decoders, requests/httpx/aiohttp responses and clients, environment or subprocess results, and simple module-local helpers through single-assignment names.",
125
- "Simple same-module wrappers preserve constructor arguments through direct stored-field accessor methods, including local identity-or-empty-dict narrowing helpers; inheritance, decorators, mutation, nested captures, unknown member escapes/helpers, and wrapper factory returns are excluded.",
132
+ "Simple same-module wrappers preserve constructor arguments through direct stored-field accessor methods, including local identity-or-empty-dict narrowing helpers; inheritance, decorators, mutation, nested captures, unknown member escapes, and unknown helpers are excluded.",
133
+ "No-argument synchronous module factories with an optional docstring and a single direct, fully bound wrapper-constructor return preserve accessor provenance; annotations alone, parameterized/async/method factories, returned aliases, and factory chains are not inferred.",
126
134
  "It diagnoses literal-key subscription and get calls; iteration and other dynamic JSON use remain out of scope.",
127
135
  "Unannotated parameters and unknown expressions are not assumed to be external; interprocedural and framework-specific boundaries can remain unreported.",
128
136
  "Names bound by loops, comprehensions, context managers, exception handlers, pattern matching, imports, or destructuring are excluded when their provenance is ambiguous; unrelated binders do not suppress the rest of a function.",
@@ -179,7 +187,8 @@ class RequirePydanticForExternalJson(Rule):
179
187
 
180
188
  first_by_origin: dict[int, tuple[ast.expr, ast.Call]] = {}
181
189
  for sink, origin in sorted(findings, key=lambda item: (item[0].lineno, item[0].col_offset)):
182
- first_by_origin.setdefault(id(origin), (sink, origin))
190
+ if not is_suppressed(source_lines, sink.lineno, self.code):
191
+ first_by_origin.setdefault(id(origin), (sink, origin))
183
192
  return [
184
193
  Diagnostic(
185
194
  path=path,
@@ -194,7 +203,6 @@ class RequirePydanticForExternalJson(Rule):
194
203
  severity=Severity.WARNING,
195
204
  )
196
205
  for sink, _origin in first_by_origin.values()
197
- if not is_suppressed(source_lines, sink.lineno, self.code)
198
206
  ]
199
207
 
200
208
 
@@ -228,8 +236,9 @@ def _module_summaries(
228
236
  continue
229
237
  _summarize_function(function, imports, module_validator_names, source_lines, decoders, records=records)
230
238
  response_callables = _response_callables(tree, imports, node_index=node_index)
231
- return _ModuleSummaries(
239
+ summaries = _ModuleSummaries(
232
240
  _wrapper_summaries(tree, imports),
241
+ {},
233
242
  decoders,
234
243
  records,
235
244
  _locally_sourced_parameters(functions),
@@ -244,6 +253,7 @@ def _module_summaries(
244
253
  _pydantic_adapter_names(tree, imports, binding_counts),
245
254
  _pydantic_model_names(tree, imports, binding_counts),
246
255
  )
256
+ return replace(summaries, wrapper_factories=_wrapper_factory_summaries(tree, imports, summaries))
247
257
 
248
258
 
249
259
  def _response_callables(
@@ -777,6 +787,9 @@ class _OriginResolver:
777
787
 
778
788
  def _call_origins(self, call: ast.Call, resolving: frozenset[str]) -> frozenset[ast.Call]:
779
789
  if isinstance(call.func, ast.Attribute) and not call.args and not call.keywords:
790
+ factory_origins = self._factory_accessor_origins(call.func)
791
+ if factory_origins is not None:
792
+ return factory_origins
780
793
  wrapped = self._wrapper_value(call.func)
781
794
  if wrapped is not None:
782
795
  return self.origins(wrapped, resolving)
@@ -799,6 +812,31 @@ class _OriginResolver:
799
812
  found_origins.update(self.origins(keyword.value, resolving))
800
813
  return frozenset(found_origins)
801
814
 
815
+ def factory_argument_origins(self, value: ast.expr, narrowing_helpers: frozenset[str]) -> frozenset[ast.Call]:
816
+ if not isinstance(value, ast.Call) or self._is_validation_call(value):
817
+ return frozenset()
818
+ if self._is_source(value):
819
+ return frozenset({value})
820
+ if (
821
+ isinstance(value.func, ast.Name)
822
+ and value.func.id in narrowing_helpers
823
+ and len(value.args) == 1
824
+ and not value.keywords
825
+ ):
826
+ return self.factory_argument_origins(value.args[0], narrowing_helpers)
827
+ return frozenset()
828
+
829
+ def _factory_accessor_origins(self, access: ast.Attribute) -> frozenset[ast.Call] | None:
830
+ receiver = self._resolved_binding(access.value) if isinstance(access.value, ast.Name) else access.value
831
+ if not isinstance(receiver, ast.Call) or not isinstance(receiver.func, ast.Name):
832
+ return None
833
+ if receiver.args or receiver.keywords or receiver.func.id in self.local_bound_names:
834
+ return None
835
+ factory = self.summaries.wrapper_factories.get(receiver.func.id)
836
+ if factory is None or not self._wrapper_is_stable(receiver, factory.wrapper):
837
+ return None
838
+ return factory.accessor_origins.get(access.attr, frozenset())
839
+
802
840
  def _wrapper_value(self, access: ast.Attribute) -> ast.expr | None:
803
841
  receiver = self._resolved_binding(access.value) if isinstance(access.value, ast.Name) else access.value
804
842
  if not isinstance(receiver, ast.Call) or not isinstance(receiver.func, ast.Name):
@@ -1691,3 +1729,69 @@ def _wrapper_descriptor(statement: ast.stmt) -> bool:
1691
1729
  if isinstance(statement, ast.Assign):
1692
1730
  return not all(isinstance(target, ast.Name) and target.id == "__slots__" for target in statement.targets)
1693
1731
  return isinstance(statement, ast.AnnAssign | ast.AugAssign)
1732
+
1733
+
1734
+ def _wrapper_factory_summaries(
1735
+ tree: ast.Module, imports: ImportIndex, summaries: _ModuleSummaries
1736
+ ) -> dict[str, _WrapperFactorySummary]:
1737
+ counts = _suite_binding_counts(tree.body)
1738
+ narrowing_helpers = _narrowing_helpers(tree, imports, counts)
1739
+ factories: dict[str, _WrapperFactorySummary] = {}
1740
+ for function in tree.body:
1741
+ if not isinstance(function, ast.FunctionDef) or counts[function.name] != 1:
1742
+ continue
1743
+ returned = _direct_factory_return(function)
1744
+ if returned is None or not isinstance(returned.func, ast.Name):
1745
+ continue
1746
+ wrapper = summaries.wrappers.get(returned.func.id)
1747
+ if wrapper is None:
1748
+ continue
1749
+ arguments = _factory_constructor_arguments(returned, wrapper)
1750
+ if arguments is None:
1751
+ continue
1752
+ scope = _own_scope(function)
1753
+ resolver = _OriginResolver(
1754
+ imports,
1755
+ summaries,
1756
+ frozenset(),
1757
+ frozenset(),
1758
+ frozenset(),
1759
+ {},
1760
+ _function_local_bound_names(function, scope),
1761
+ scope,
1762
+ )
1763
+ origins = {
1764
+ accessor: resolver.factory_argument_origins(arguments[wrapper.fields[field]], narrowing_helpers)
1765
+ for accessor, field in wrapper.accessors.items()
1766
+ }
1767
+ if any(origins.values()):
1768
+ factories[function.name] = _WrapperFactorySummary(wrapper, origins)
1769
+ return factories
1770
+
1771
+
1772
+ def _direct_factory_return(function: ast.FunctionDef) -> ast.Call | None:
1773
+ statements = function.body[1:] if ast.get_docstring(function, clean=False) is not None else function.body
1774
+ if (
1775
+ function.decorator_list
1776
+ or _parameter_positions(function)
1777
+ or any((function.args.kwonlyargs, function.args.vararg, function.args.kwarg, function.type_params))
1778
+ or len(statements) != 1
1779
+ ):
1780
+ return None
1781
+ statement = statements[0]
1782
+ if not isinstance(statement, ast.Return) or not isinstance(statement.value, ast.Call):
1783
+ return None
1784
+ if any(isinstance(node, ast.NamedExpr | ast.Yield | ast.YieldFrom | ast.Await) for node in ast.walk(statement)):
1785
+ return None
1786
+ return statement.value
1787
+
1788
+
1789
+ def _factory_constructor_arguments(call: ast.Call, wrapper: _WrapperSummary) -> dict[str, ast.expr] | None:
1790
+ if len(call.args) > len(wrapper.parameters) or any(isinstance(argument, ast.Starred) for argument in call.args):
1791
+ return None
1792
+ arguments = dict(zip(wrapper.parameters, call.args, strict=False))
1793
+ for keyword in call.keywords:
1794
+ if keyword.arg not in wrapper.parameters or keyword.arg in arguments:
1795
+ return None
1796
+ arguments[keyword.arg] = keyword.value
1797
+ return arguments if len(arguments) == len(wrapper.parameters) else None