sarj-python-lint 1.88.1__tar.gz → 1.88.20__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 (302) hide show
  1. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_fastapi.py +185 -10
  4. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +5 -5
  5. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +59 -21
  6. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/.gitignore +0 -0
  7. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/LICENSE +0 -0
  8. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/README.md +0 -0
  9. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/__init__.py +0 -0
  10. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/__main__.py +0 -0
  11. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix.py +0 -0
  13. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  14. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  15. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_comments.py +0 -0
  16. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  17. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  18. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_docs.py +0 -0
  19. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_enums.py +0 -0
  20. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  21. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_inert.py +0 -0
  22. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  23. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  24. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_security.py +0 -0
  25. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  26. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_autofix_tests.py +0 -0
  27. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_file_context.py +0 -0
  28. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_filesystem.py +0 -0
  29. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_fixes.py +0 -0
  30. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_parallel.py +0 -0
  31. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_prepared_sources.py +0 -0
  32. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_python_target.py +0 -0
  33. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  34. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_secret_names.py +0 -0
  35. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_source.py +0 -0
  36. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/_version.py +0 -0
  37. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/application_logging.py +0 -0
  38. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/autofix.py +0 -0
  39. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/benchmark.py +0 -0
  40. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/cast_access.py +0 -0
  41. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/interpreter_argv.py +0 -0
  42. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/json_boundary.py +0 -0
  43. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/py.typed +0 -0
  44. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/ratchet.py +0 -0
  45. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rule_base.py +0 -0
  46. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/__init__.py +0 -0
  47. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  48. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  49. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  50. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  51. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  52. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  53. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
  54. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  55. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  56. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_comments.py +0 -0
  57. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  58. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  59. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  60. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  61. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  62. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  63. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_exports.py +0 -0
  64. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_first_party.py +0 -0
  65. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  66. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_imports.py +0 -0
  67. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  68. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  69. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  70. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_local_source.py +0 -0
  71. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_logging.py +0 -0
  72. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  73. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  74. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  75. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  76. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  77. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  78. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
  79. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_paths.py +0 -0
  80. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_postgres.py +0 -0
  81. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_project_index.py +0 -0
  82. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  83. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  84. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_pytest.py +0 -0
  85. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_registry.py +0 -0
  86. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  87. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  88. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  89. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  90. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  91. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  92. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_sql.py +0 -0
  93. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  94. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  95. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  96. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  97. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_store_sql.py +0 -0
  98. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  99. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  100. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  101. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  102. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  103. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  104. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  105. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  106. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  107. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  108. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  109. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  110. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  111. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  112. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  113. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  114. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  115. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  116. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  117. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  118. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  119. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  120. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  121. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  122. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  123. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  124. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  125. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  126. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  127. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  128. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  129. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  130. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  131. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  132. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  133. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  134. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  135. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  136. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  137. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  138. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  139. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  140. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
  141. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  142. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  143. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  144. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
  145. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  146. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  147. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  148. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  149. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  150. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  151. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  152. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  153. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  154. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  155. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  156. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  157. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  158. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  159. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  160. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  161. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  162. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  163. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  164. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  165. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  166. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  167. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  168. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  169. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  170. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  171. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  172. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  173. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  174. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  175. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  176. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  177. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  178. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  179. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  180. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  181. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  182. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
  183. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  184. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  185. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  186. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
  187. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  188. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  189. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  190. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  191. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  192. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  193. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  194. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  195. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  196. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  197. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  198. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  199. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  200. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  201. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  202. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  203. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  204. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  205. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  206. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  207. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  208. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  209. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  210. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  211. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  212. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  213. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
  214. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  215. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  216. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  217. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  218. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  219. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  220. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  221. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  222. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  223. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  224. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  225. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  226. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  227. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  228. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  229. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  230. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  231. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  232. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  233. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  234. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  235. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  236. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  237. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  238. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
  239. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  240. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  241. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  242. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  243. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  244. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  245. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  246. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  247. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  248. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
  249. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  250. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  251. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  252. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  253. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  254. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
  255. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
  256. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  257. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  258. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  259. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  260. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  261. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  262. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  263. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  264. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  265. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  266. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  267. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  268. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  269. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  270. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  271. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  272. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  273. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  274. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  275. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  276. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  277. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  278. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  279. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  280. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  281. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  282. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  283. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  284. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  285. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  286. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  287. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  288. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/stepdown.py +0 -0
  289. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  290. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  291. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
  292. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  293. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
  294. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  295. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  296. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  297. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  298. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  299. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  300. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  301. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  302. {sarj_python_lint-1.88.1 → sarj_python_lint-1.88.20}/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.88.1
3
+ Version: 1.88.20
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.88.1"
3
+ version = "1.88.20"
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" }]
@@ -3,6 +3,10 @@ from __future__ import annotations
3
3
  import ast
4
4
  from collections import Counter
5
5
  from dataclasses import dataclass
6
+ from functools import cached_property
7
+ from http import HTTPStatus
8
+ import re
9
+ from types import MappingProxyType
6
10
  from typing import TYPE_CHECKING, Literal, NamedTuple
7
11
 
8
12
  from sarj_python_lint._source import read_python_source
@@ -19,19 +23,24 @@ HTTP_METHODS = frozenset({"get", "post", "put", "patch", "delete", "options", "h
19
23
  PARAM_MARKERS = frozenset({"Path", "Query", "Header", "Cookie", "Body", "Form", "File", "Depends", "Security"})
20
24
  SCHEMA_MARKERS = PARAM_MARKERS - {"Depends", "Security"}
21
25
  INJECTION_TYPES = frozenset({"Request", "Response", "WebSocket", "HTTPConnection", "BackgroundTasks", "SecurityScopes"})
22
- RESPONSE_TYPES = frozenset(
26
+ _RESPONSE_DEFAULT_STATUSES = MappingProxyType(
23
27
  {
24
- "Response",
25
- "JSONResponse",
26
- "HTMLResponse",
27
- "PlainTextResponse",
28
- "RedirectResponse",
29
- "StreamingResponse",
30
- "FileResponse",
31
- "ORJSONResponse",
32
- "UJSONResponse",
28
+ "Response": HTTPStatus.OK,
29
+ "JSONResponse": HTTPStatus.OK,
30
+ "HTMLResponse": HTTPStatus.OK,
31
+ "PlainTextResponse": HTTPStatus.OK,
32
+ "RedirectResponse": HTTPStatus.TEMPORARY_REDIRECT,
33
+ "StreamingResponse": HTTPStatus.OK,
34
+ "FileResponse": HTTPStatus.OK,
35
+ "ORJSONResponse": HTTPStatus.OK,
36
+ "UJSONResponse": HTTPStatus.OK,
33
37
  }
34
38
  )
39
+ RESPONSE_TYPES = frozenset(_RESPONSE_DEFAULT_STATUSES)
40
+ _STARLETTE_RESPONSE_TYPES = RESPONSE_TYPES - {"ORJSONResponse", "UJSONResponse"}
41
+ _JSON_RESPONSE_TYPES = frozenset({"JSONResponse", "ORJSONResponse", "UJSONResponse"})
42
+ NO_BODY_STATUSES = frozenset({204, 205, 304})
43
+ _STATUS_MEMBER = re.compile(r"HTTP_(\d{3})_[A-Z0-9_]+")
35
44
  _DEPENDENCY_MARKERS = frozenset({"Depends", "Security"})
36
45
  _CONSTRUCTOR_SOURCES = frozenset({"fastapi", "fastapi.applications", "fastapi.routing"})
37
46
  _MAX_IMPORTED_MODULE_BYTES = 500_000
@@ -50,6 +59,7 @@ class Route:
50
59
  inherited_responses: ast.expr | None = None
51
60
  inherited_response_class: ast.expr | None = None
52
61
  inherited_prefix: ast.expr | None = None
62
+ inherited_has_unpack: bool = False
53
63
 
54
64
  @property
55
65
  def keywords(self) -> dict[str, ast.expr]:
@@ -154,6 +164,7 @@ class FastapiIndex:
154
164
  self.receiver_kinds: dict[_ReceiverKey, Literal["FastAPI", "APIRouter"]] = {}
155
165
  self.hidden_receivers: set[_ReceiverKey] = set()
156
166
  self.receiver_defaults: dict[_ReceiverKey, dict[str, ast.expr]] = {}
167
+ self.unpacked_receivers: set[_ReceiverKey] = set()
157
168
  self.decorators: dict[_ReceiverKey, _DecoratorBinding] = {}
158
169
  self.bound_names: set[_ReceiverKey] = set()
159
170
  self._node_scopes: dict[int, int] = {}
@@ -394,6 +405,8 @@ class FastapiIndex:
394
405
  for keyword in value.keywords
395
406
  if keyword.arg in {"responses", "default_response_class", "prefix"}
396
407
  }
408
+ if any(keyword.arg is None for keyword in value.keywords):
409
+ self.unpacked_receivers.add(key)
397
410
  if self._constructor_is_hidden(value):
398
411
  self.hidden_receivers.add(key)
399
412
 
@@ -497,6 +510,7 @@ class FastapiIndex:
497
510
  inherited_responses=defaults.get("responses"),
498
511
  inherited_response_class=defaults.get("default_response_class"),
499
512
  inherited_prefix=defaults.get("prefix"),
513
+ inherited_has_unpack=origin in self.unpacked_receivers,
500
514
  )
501
515
  for route_method in route_methods
502
516
  )
@@ -541,6 +555,167 @@ class FastapiIndex:
541
555
  case _:
542
556
  return ""
543
557
 
558
+ def status_code(self, node: ast.expr | None) -> int | None:
559
+ if isinstance(node, ast.Constant) and isinstance(node.value, int):
560
+ return node.value
561
+ if not isinstance(node, (ast.Name, ast.Attribute)) or self._is_shadowed_in_enclosing_scope(node):
562
+ return None
563
+ qualified = self.imports.resolved_qualified_name(node) or ""
564
+ module, _, member = qualified.rpartition(".")
565
+ if (
566
+ module == "starlette.status"
567
+ and module not in self._unstable_native_providers
568
+ and (match := _STATUS_MEMBER.fullmatch(member))
569
+ ):
570
+ return int(match.group(1))
571
+ if not isinstance(node, ast.Attribute):
572
+ return None
573
+ owner = self._status_provider(node.value)
574
+ if owner in self._unstable_native_providers:
575
+ return None
576
+ if owner == "starlette.status" and (match := _STATUS_MEMBER.fullmatch(node.attr)):
577
+ return int(match.group(1))
578
+ if owner == "http.HTTPStatus":
579
+ status = HTTPStatus.__members__.get(node.attr)
580
+ return status.value if status is not None else None
581
+ return None
582
+
583
+ def operation_status_code(self, route: Route) -> int | None:
584
+ node = route.keywords.get("status_code")
585
+ if node is None and route.has_unpack:
586
+ return None
587
+ if node is None or (isinstance(node, ast.Constant) and node.value is None):
588
+ if not self.response_classes_are_stable:
589
+ return None
590
+ if route.has_unpack and "response_class" not in route.keywords:
591
+ return None
592
+ response_class = route.keywords.get("response_class") or route.inherited_response_class
593
+ if response_class is None:
594
+ return None if route.inherited_has_unpack else HTTPStatus.OK
595
+ response_name = self.response_class_name(response_class)
596
+ return _RESPONSE_DEFAULT_STATUSES.get(response_name) if response_name is not None else None
597
+ return self.status_code(node)
598
+
599
+ def response_class_name(self, node: ast.expr) -> str | None:
600
+ if not self.response_classes_are_stable:
601
+ return None
602
+ return self._native_response_name(self._response_import(node))
603
+
604
+ def can_infer_json_response(self, route: Route) -> bool:
605
+ if route.has_unpack:
606
+ return False
607
+ status = self.operation_status_code(route)
608
+ if status is None or status in NO_BODY_STATUSES:
609
+ return False
610
+ response_class = route.keywords.get("response_class") or route.inherited_response_class
611
+ if response_class is None:
612
+ return not route.inherited_has_unpack and self.response_classes_are_stable
613
+ return self.response_class_name(response_class) in _JSON_RESPONSE_TYPES
614
+
615
+ @property
616
+ def response_classes_are_stable(self) -> bool:
617
+ return "responses" not in self._unstable_native_providers
618
+
619
+ @staticmethod
620
+ def _native_response_name(qualified: str | None) -> str | None:
621
+ if qualified is None:
622
+ return None
623
+ module, _, name = qualified.rpartition(".")
624
+ if module == "fastapi" and name == "Response":
625
+ return name
626
+ if module == "fastapi.responses" and name in RESPONSE_TYPES:
627
+ return name
628
+ if module == "starlette.responses" and name in _STARLETTE_RESPONSE_TYPES:
629
+ return name
630
+ return None
631
+
632
+ def _response_import(self, node: ast.expr) -> str | None:
633
+ if not isinstance(node, (ast.Name, ast.Attribute)) or self._is_shadowed_in_enclosing_scope(node):
634
+ return None
635
+ qualified = self.imports.resolved_qualified_name(node)
636
+ if isinstance(node, ast.Name) and qualified is None:
637
+ binding = self.imports.bindings.get(node.id)
638
+ return binding.module if binding is not None and binding.symbol is None else None
639
+ if isinstance(node, ast.Attribute) and qualified is None:
640
+ module = self.imports.resolved_qualified_name(node.value)
641
+ return f"{module}.{node.attr}" if module is not None else None
642
+ return qualified
643
+
644
+ def _status_provider(self, node: ast.expr) -> str | None:
645
+ if not isinstance(node, (ast.Name, ast.Attribute)) or self._is_shadowed_in_enclosing_scope(node):
646
+ return None
647
+ qualified = self.imports.resolved_qualified_name(node)
648
+ if isinstance(node, ast.Name) and qualified is None:
649
+ binding = self.imports.bindings.get(node.id)
650
+ qualified = (
651
+ binding.module
652
+ if binding is not None and binding.symbol is None and binding.module != "fastapi.status"
653
+ else None
654
+ )
655
+ # FastAPI exports Starlette's status module rather than a status submodule.
656
+ if qualified == "fastapi.status":
657
+ return "starlette.status"
658
+ return qualified if qualified in {"starlette.status", "http.HTTPStatus"} else None
659
+
660
+ @cached_property
661
+ def _unstable_native_providers(self) -> frozenset[str]:
662
+ providers: set[str] = set()
663
+ route_calls: set[int] = set()
664
+ for node in walk_ast(self.tree):
665
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
666
+ route_calls.update(id(route.decorator) for route in self.routes(node))
667
+ continue
668
+ values = self._provider_values(node)
669
+ if not values:
670
+ continue
671
+ for value in values:
672
+ if (provider := self._status_provider(value)) is not None:
673
+ providers.add(provider)
674
+ for value in self._response_provider_values(node, values, route_calls):
675
+ qualified = self._response_import(value)
676
+ if qualified in {"fastapi.responses", "starlette.responses"} or self._native_response_name(qualified):
677
+ providers.add("responses")
678
+ return frozenset(providers)
679
+
680
+ @staticmethod
681
+ def _provider_values(node: ast.AST) -> tuple[ast.expr, ...]:
682
+ match node:
683
+ case ast.Attribute(value=value, ctx=ast.Store() | ast.Del()):
684
+ return node, value
685
+ case ast.Assign(value=value) | ast.AnnAssign(value=ast.expr() as value) | ast.NamedExpr(value=value):
686
+ return (value,)
687
+ case ast.Return(value=ast.expr() as value):
688
+ return (value,)
689
+ case ast.Call(args=args, keywords=keywords):
690
+ return (*args, *(keyword.value for keyword in keywords))
691
+ case _:
692
+ return ()
693
+
694
+ def _response_provider_values(
695
+ self, node: ast.AST, values: tuple[ast.expr, ...], route_calls: set[int]
696
+ ) -> tuple[ast.expr, ...]:
697
+ if not isinstance(node, ast.Call):
698
+ return values
699
+ trusted_keyword = self._trusted_response_keyword(node, route_calls)
700
+ return (*node.args, *(keyword.value for keyword in node.keywords if keyword.arg != trusted_keyword))
701
+
702
+ def _trusted_response_keyword(self, node: ast.Call, route_calls: set[int]) -> str | None:
703
+ trusted_keyword = "response_class" if id(node) in route_calls else None
704
+ if (
705
+ any(keyword.arg == "default_response_class" for keyword in node.keywords)
706
+ and self.constructor_kind(node) is not None
707
+ ):
708
+ return "default_response_class"
709
+ if not isinstance(node.func, ast.Attribute) or node.func.attr not in {"include_router", "add_api_route"}:
710
+ return trusted_keyword
711
+ scope = self._node_scopes.get(id(node), 0)
712
+ receiver = _binding_name(node.func.value)
713
+ if receiver.startswith("self."):
714
+ scope = self._node_classes.get(id(node)) or scope
715
+ if self._receiver_key(scope, receiver) is None:
716
+ return trusted_keyword
717
+ return "default_response_class" if node.func.attr == "include_router" else "response_class"
718
+
544
719
  @staticmethod
545
720
  def _root_name(node: ast.Attribute) -> str:
546
721
  value: ast.expr = node
@@ -27,7 +27,6 @@ if TYPE_CHECKING:
27
27
  from sarj_python_lint.rules._ast_index import NodeIndex
28
28
 
29
29
 
30
- _NO_BODY = frozenset({204, 304})
31
30
  _NON_JSON_RESPONSES = frozenset(
32
31
  {"FileResponse", "HTMLResponse", "PlainTextResponse", "RedirectResponse", "StreamingResponse"}
33
32
  )
@@ -56,7 +55,9 @@ class FastapiClassRouterContract(Rule):
56
55
  autofix=AutofixPolicy.NONE,
57
56
  limitations=(
58
57
  "Tests, generated code, and hidden routes are excluded. FastAPI application construction and include_router composition are allowed.",
59
- "Response-model checks exclude 204/304 responses and explicit stream/file/HTML/text/redirect responses.",
58
+ "Response-model checks exclude literal or import-proven 204/205/304 statuses and proven non-JSON response classes, including raw Response.",
59
+ "Unpacked metadata or an explicit unresolved status cannot prove a JSON response; receiver ownership remains checked while response-model inference abstains.",
60
+ "Response classes require unshadowed native import provenance; direct provider writes/escapes, custom classes, and unresolved defaults disable JSON-model inference.",
60
61
  ),
61
62
  examples=(
62
63
  RuleExample(
@@ -211,12 +212,11 @@ def _response_problem(
211
212
  function: ast.FunctionDef | ast.AsyncFunctionDef, route: Route, index: FastapiIndex
212
213
  ) -> str | None:
213
214
  keywords = route.keywords
214
- status = keywords.get("status_code")
215
- if isinstance(status, ast.Constant) and status.value in _NO_BODY:
216
- return None
217
215
  response_class = keywords.get("response_class") or route.inherited_response_class
218
216
  if response_class is not None and flat_name(response_class) in _NON_JSON_RESPONSES:
219
217
  return None
218
+ if not index.can_infer_json_response(route):
219
+ return None
220
220
  response_model = keywords.get("response_model")
221
221
  if response_model is None or (isinstance(response_model, ast.Constant) and response_model.value is None):
222
222
  return "JSON operation requires explicit `response_model=NamedResponse`"
@@ -20,6 +20,7 @@ from sarj_python_lint.rule_base import (
20
20
  )
21
21
  from sarj_python_lint.rules._ast_index import children
22
22
  from sarj_python_lint.rules._fastapi import (
23
+ NO_BODY_STATUSES,
23
24
  SCHEMA_MARKERS,
24
25
  FastapiIndex,
25
26
  Route,
@@ -89,6 +90,9 @@ class FastapiExplicitOpenapiContract(Rule):
89
90
  limitations=(
90
91
  "Hidden routes, WebSocket handlers, tests, generated files, documentation-source examples, and unrelated decorators are excluded.",
91
92
  "Dynamic response mappings are accepted when their contents cannot be resolved statically.",
93
+ "Response-body/model inference abstains for unresolved status/class metadata, direct native-provider writes/escapes, and opaque unpacked defaults; no-body JSON metadata includes 205.",
94
+ "Direct-status alias/callee/catch recognition retains its existing limits; broader coverage requires separate reachability and runtime-dispatch evidence.",
95
+ "Route conflicts require unconditional registrations on the same local router; conditional composition requires trusted route/schema validation.",
92
96
  "Operation ID conflicts require unconditional registrations with literal IDs and paths on the same local router; conditional registrations, dynamic IDs, OpenAPI overrides, and cross-router composition require generated-schema validation.",
93
97
  "Explicit status and parameter-location findings are organizational policy even when FastAPI inference would generate valid OpenAPI.",
94
98
  (
@@ -268,7 +272,7 @@ class FastapiExplicitOpenapiContract(Rule):
268
272
  findings.extend(_check_metadata(route, index))
269
273
  findings.extend(_check_projection(route))
270
274
  findings.extend(_check_response_statuses(route, index))
271
- findings.extend(_check_route_conflicts(declared))
275
+ findings.extend(_check_route_conflicts(declared, context.parents))
272
276
  findings.extend(_check_operation_ids(declared, context.parents))
273
277
  return [
274
278
  Diagnostic(
@@ -548,7 +552,9 @@ def _check_inferred_return(
548
552
  if not builds_fixed_record(function, imports=index.imports):
549
553
  return []
550
554
  for route in _operations(routes):
551
- if route.has_unpack:
555
+ if route.inherited_has_unpack and route.inherited_responses is None:
556
+ continue
557
+ if not index.can_infer_json_response(route):
552
558
  continue
553
559
  response_model = route.keywords.get("response_model")
554
560
  status = _status_code(route.keywords.get("status_code"), index) or HTTPStatus.OK
@@ -572,13 +578,22 @@ def _check_return_route(route: Route, contract: _ReturnContract, index: FastapiI
572
578
  if contract.erases_schema and not _concrete_response_model(response_model, index):
573
579
  problems.add("response annotation erases its OpenAPI schema; define an output model")
574
580
  status = _status_code(keywords.get("status_code"), index)
581
+ proven_status = index.operation_status_code(route)
582
+ body_status_is_known = proven_status is not None and proven_status not in NO_BODY_STATUSES
575
583
  response_class = keywords.get("response_class") or route.inherited_response_class
576
- if contract.is_response and status not in _NO_CONTENT_STATUSES and response_class is None:
584
+ default_class_is_known = not route.inherited_has_unpack and index.response_classes_are_stable
585
+ if (
586
+ contract.is_response
587
+ and status not in _NO_CONTENT_STATUSES
588
+ and body_status_is_known
589
+ and response_class is None
590
+ and default_class_is_known
591
+ ):
577
592
  problems.add("direct Response return requires response_class= for accurate OpenAPI")
578
593
  _check_response_body(route, contract, index, problems)
579
594
  if response_model is not None and not response_model_none and _schema_erasing(response_model, index):
580
595
  problems.add("response_model erases its OpenAPI schema; define an output model")
581
- if status in _NO_CONTENT_STATUSES:
596
+ if status in _NO_CONTENT_STATUSES and status == proven_status:
582
597
  if response_model is not None and not response_model_none:
583
598
  problems.add(f"status {status} must not declare a response model")
584
599
  if not (contract.is_none or contract.is_response):
@@ -589,19 +604,30 @@ def _check_response_body(route: Route, contract: _ReturnContract, index: Fastapi
589
604
  response_model = route.keywords.get("response_model")
590
605
  response_model_none = response_model is not None and _literal_none(response_model)
591
606
  status = _status_code(route.keywords.get("status_code"), index)
607
+ proven_status = index.operation_status_code(route)
592
608
  response_class = route.keywords.get("response_class") or route.inherited_response_class
593
609
  response_name = index.response_name(contract.annotation)
594
610
  class_name = index.response_name(response_class)
595
- response_class_is_unresolved = response_class is not None and not class_name
611
+ proven_class_name = index.response_class_name(response_class) if response_class is not None else None
612
+ response_class_is_unresolved = (response_class is not None and (not class_name or proven_class_name is None)) or (
613
+ response_class is None and (route.inherited_has_unpack or not index.response_classes_are_stable)
614
+ )
596
615
  has_documented_body = (
597
616
  _concrete_response_model(response_model, index)
598
617
  or any(_documents_response_content(node, status, index) for node in _route_response_nodes(route))
599
618
  or class_name in _SELF_DOCUMENTING_RESPONSE_CLASSES
619
+ or proven_class_name in _SELF_DOCUMENTING_RESPONSE_CLASSES
600
620
  or response_class_is_unresolved
601
621
  )
602
- if response_name not in {"", "Response"} and class_name and response_name != class_name:
622
+ if (
623
+ response_name not in {"", "Response"}
624
+ and class_name
625
+ and proven_class_name is not None
626
+ and response_name != class_name
627
+ ):
603
628
  problems.add(f"return type {response_name} conflicts with response_class={class_name}")
604
- if contract.is_response and status not in _NO_CONTENT_STATUSES and not has_documented_body:
629
+ body_status_is_known = proven_status is not None and proven_status not in NO_BODY_STATUSES
630
+ if contract.is_response and status not in _NO_CONTENT_STATUSES and body_status_is_known and not has_documented_body:
605
631
  problems.add("direct Response return requires a concrete response_model or responses content schema")
606
632
  elif not contract.is_response and not contract.is_none and response_model_none:
607
633
  problems.add("response_model=None suppresses the declared response schema")
@@ -725,14 +751,7 @@ def _check_direct_responses(
725
751
  if not direct.statuses:
726
752
  return findings
727
753
  for route in _operations(routes):
728
- responses = _route_response_nodes(route)
729
- if any(not isinstance(node, ast.Dict) for node in responses):
730
- continue
731
- documented = {
732
- status for node in responses if isinstance(node, ast.Dict) for status in _response_codes(node, index)
733
- }
734
- primary = _status_code(route.keywords.get("status_code"), index)
735
- missing = sorted(direct.statuses - documented - ({primary} if primary is not None else set()))
754
+ missing = _undocumented_direct_statuses(route, direct.statuses, index)
736
755
  if missing:
737
756
  findings.append(
738
757
  _Finding(
@@ -743,6 +762,23 @@ def _check_direct_responses(
743
762
  return findings
744
763
 
745
764
 
765
+ def _undocumented_direct_statuses(route: Route, statuses: set[int], index: FastapiIndex) -> list[int]:
766
+ if route.has_unpack or (route.inherited_has_unpack and route.inherited_responses is None):
767
+ return []
768
+ proven_primary = index.operation_status_code(route)
769
+ if proven_primary is None:
770
+ return []
771
+ responses = _route_response_nodes(route)
772
+ if any(not isinstance(node, ast.Dict) for node in responses):
773
+ return []
774
+ documented = {status for node in responses if isinstance(node, ast.Dict) for status in _response_codes(node, index)}
775
+ primary = _status_code(route.keywords.get("status_code"), index)
776
+ primary_statuses = {proven_primary}
777
+ if primary is not None:
778
+ primary_statuses.add(primary)
779
+ return sorted(statuses - documented - primary_statuses)
780
+
781
+
746
782
  @dataclass(frozen=True, slots=True)
747
783
  class _DirectResponseStatuses:
748
784
  statuses: set[int]
@@ -794,13 +830,15 @@ def _direct_statuses(
794
830
  statuses: list[tuple[ast.Call, int]] = []
795
831
  call = current.exc if isinstance(current, ast.Raise) else None
796
832
  if isinstance(call, ast.Call) and index.is_http_exception(call.func) and not catches_http_exception:
797
- status = _status_code(_keyword(call, "status_code") or (call.args[0] if call.args else None), index)
798
- if status is not None:
833
+ status_node = _keyword(call, "status_code") or (call.args[0] if call.args else None)
834
+ status = _status_code(status_node, index)
835
+ if status is not None and index.status_code(status_node) == status:
799
836
  statuses.append((call, status))
800
837
  returned = current.value if isinstance(current, ast.Return) else None
801
838
  if isinstance(returned, ast.Call) and index.is_response(returned.func):
802
- status = _status_code(_keyword(returned, "status_code"), index)
803
- if status is not None:
839
+ status_node = _keyword(returned, "status_code")
840
+ status = _status_code(status_node, index)
841
+ if status is not None and index.status_code(status_node) == status:
804
842
  statuses.append((returned, status))
805
843
  return statuses
806
844
 
@@ -890,12 +928,12 @@ def _function_scopes(tree: ast.Module) -> dict[int, int]:
890
928
  return scopes
891
929
 
892
930
 
893
- def _check_route_conflicts(routes: list[tuple[int, Route]]) -> list[_Finding]:
931
+ def _check_route_conflicts(routes: list[tuple[int, Route]], parents: Mapping[ast.AST, ast.AST]) -> list[_Finding]:
894
932
  findings: list[_Finding] = []
895
933
  seen: dict[tuple[int, str, str, str], Route] = {}
896
934
  dynamic: dict[tuple[int, str, str], list[tuple[Route, re.Pattern[str]]]] = {}
897
935
  for scope, route in sorted(routes, key=lambda item: item[1].decorator.lineno):
898
- if route.path is None or route.method == "*":
936
+ if route.path is None or route.method == "*" or _conditional_registration(route.decorator, parents):
899
937
  continue
900
938
  key = scope, route.receiver, route.method, route.path
901
939
  if key in seen: