sarj-python-lint 1.76.0__tar.gz → 1.77.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_fastapi.py +21 -17
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +53 -4
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +86 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/.gitignore +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/LICENSE +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/README.md +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/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.
|
|
3
|
+
Version: 1.77.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -32,6 +32,7 @@ RESPONSE_TYPES = frozenset(
|
|
|
32
32
|
}
|
|
33
33
|
)
|
|
34
34
|
_DEPENDENCY_MARKERS = frozenset({"Depends", "Security"})
|
|
35
|
+
_CONSTRUCTOR_SOURCES = frozenset({"fastapi", "fastapi.applications", "fastapi.routing"})
|
|
35
36
|
_MAX_IMPORTED_MODULE_BYTES = 500_000
|
|
36
37
|
_MAX_IMPORT_HOPS = 8
|
|
37
38
|
_MAX_PATH_ANCESTORS = 32
|
|
@@ -134,6 +135,7 @@ class FastapiIndex:
|
|
|
134
135
|
self._node_classes: dict[int, int | None] = {}
|
|
135
136
|
self._route_scopes: dict[int, int] = {}
|
|
136
137
|
self._scope_parents: dict[int, int | None] = {0: None}
|
|
138
|
+
self._class_scopes: set[int] = set()
|
|
137
139
|
self._index_scopes(tree)
|
|
138
140
|
self._read_imports(tree)
|
|
139
141
|
self._read_aliases(tree)
|
|
@@ -145,6 +147,7 @@ class FastapiIndex:
|
|
|
145
147
|
self._node_scopes[id(node)] = scope
|
|
146
148
|
if isinstance(node, ast.ClassDef):
|
|
147
149
|
class_owner = node.lineno
|
|
150
|
+
self._class_scopes.add(node.lineno)
|
|
148
151
|
self._scope_parents[node.lineno] = scope
|
|
149
152
|
scope = node.lineno
|
|
150
153
|
self._node_classes[id(node)] = class_owner
|
|
@@ -272,7 +275,7 @@ class FastapiIndex:
|
|
|
272
275
|
|
|
273
276
|
def _resolve_alias(self, key: _ReceiverKey, value: ast.expr) -> bool:
|
|
274
277
|
source = self._receiver_key(key.scope, _binding_name(value))
|
|
275
|
-
constructor_kind = self.
|
|
278
|
+
constructor_kind = self.constructor_kind(value)
|
|
276
279
|
if constructor_kind is not None:
|
|
277
280
|
self._register_constructor(key, value, constructor_kind)
|
|
278
281
|
return True
|
|
@@ -337,25 +340,26 @@ class FastapiIndex:
|
|
|
337
340
|
return isinstance(value, ast.Constant) and value.value is False
|
|
338
341
|
|
|
339
342
|
def _is_constructor_call(self, node: ast.expr) -> bool:
|
|
340
|
-
return self.
|
|
343
|
+
return self.constructor_kind(node) is not None
|
|
341
344
|
|
|
342
|
-
def
|
|
345
|
+
def constructor_kind(self, node: ast.expr) -> Literal["FastAPI", "APIRouter"] | None:
|
|
343
346
|
if not isinstance(node, ast.Call):
|
|
344
347
|
return None
|
|
345
|
-
if isinstance(node.func, ast.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
348
|
+
root = self._root_name(node.func) if isinstance(node.func, ast.Attribute) else flat_name(node.func)
|
|
349
|
+
local_scope = self._node_scopes.get(id(node), 0)
|
|
350
|
+
scope: int | None = local_scope
|
|
351
|
+
while scope is not None and scope != 0:
|
|
352
|
+
# Method bodies do not close over their class namespace.
|
|
353
|
+
if (scope == local_scope or scope not in self._class_scopes) and _ReceiverKey(
|
|
354
|
+
scope, root
|
|
355
|
+
) in self.bound_names:
|
|
356
|
+
return None
|
|
357
|
+
scope = self._scope_parents[scope]
|
|
358
|
+
canonical = self.imports.resolved_symbol(node.func, sources=_CONSTRUCTOR_SOURCES)
|
|
359
|
+
if canonical == "FastAPI":
|
|
360
|
+
return "FastAPI"
|
|
361
|
+
if canonical == "APIRouter":
|
|
362
|
+
return "APIRouter"
|
|
359
363
|
return None
|
|
360
364
|
|
|
361
365
|
def routes(self, function: ast.FunctionDef | ast.AsyncFunctionDef) -> tuple[Route, ...]:
|
|
@@ -42,19 +42,21 @@ class FastapiClassRouterContract(Rule):
|
|
|
42
42
|
id = "fastapi-class-router-contract"
|
|
43
43
|
code = "SARJ451"
|
|
44
44
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
45
|
-
summary="FastAPI
|
|
45
|
+
summary="FastAPI HTTP routes use injected `*Router.build()` owners and explicit named object response models.",
|
|
46
46
|
rationale=(
|
|
47
47
|
"Class-owned construction gives dependencies one reviewable composition boundary, while explicit object models "
|
|
48
48
|
"keep success envelopes stable and generated clients precise."
|
|
49
49
|
),
|
|
50
50
|
remediation=(
|
|
51
51
|
"Inject dependencies through `*Router.__init__`, create and return a local `APIRouter` from `build()`, and declare "
|
|
52
|
-
"`response_model=NamedResponse` with a matching named return annotation."
|
|
52
|
+
"`response_model=NamedResponse` with a matching named return annotation. Compose the application with "
|
|
53
|
+
"`FastAPI.include_router(...)` instead of declaring HTTP handlers directly on it."
|
|
53
54
|
),
|
|
54
55
|
category=RuleCategory.ARCHITECTURE,
|
|
55
56
|
autofix=AutofixPolicy.NONE,
|
|
56
57
|
limitations=(
|
|
57
|
-
"Tests, generated code, hidden routes
|
|
58
|
+
"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
60
|
),
|
|
59
61
|
examples=(
|
|
60
62
|
RuleExample(
|
|
@@ -66,6 +68,38 @@ class FastapiClassRouterContract(Rule):
|
|
|
66
68
|
expected_count=1,
|
|
67
69
|
public=True,
|
|
68
70
|
),
|
|
71
|
+
RuleExample(
|
|
72
|
+
example_id="direct-app-route",
|
|
73
|
+
title="An application declares an HTTP handler directly",
|
|
74
|
+
outcome=ExampleOutcome.MATCH,
|
|
75
|
+
files=(
|
|
76
|
+
ExampleFile.python(
|
|
77
|
+
"app/routes.py",
|
|
78
|
+
"from fastapi import FastAPI\napp = FastAPI()\n"
|
|
79
|
+
"@app.get('/items', response_model=ItemsResponse)\n"
|
|
80
|
+
"def items() -> ItemsResponse:\n return ItemsResponse(items=[])\n",
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
focus_path=PurePosixPath("app/routes.py"),
|
|
84
|
+
expected_count=1,
|
|
85
|
+
public=True,
|
|
86
|
+
scenario="application-composition",
|
|
87
|
+
),
|
|
88
|
+
RuleExample(
|
|
89
|
+
example_id="app-router-composition",
|
|
90
|
+
title="An application includes a class-owned router",
|
|
91
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
92
|
+
files=(
|
|
93
|
+
ExampleFile.python(
|
|
94
|
+
"app/routes.py",
|
|
95
|
+
"from fastapi import FastAPI\napp = FastAPI()\napp.include_router(ItemRouter(service).build())\n",
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
focus_path=PurePosixPath("app/routes.py"),
|
|
99
|
+
expected_count=0,
|
|
100
|
+
public=True,
|
|
101
|
+
scenario="application-composition",
|
|
102
|
+
),
|
|
69
103
|
RuleExample(
|
|
70
104
|
example_id="class-router",
|
|
71
105
|
title="A class builds and returns its router",
|
|
@@ -99,6 +133,7 @@ class FastapiClassRouterContract(Rule):
|
|
|
99
133
|
node for node in context.nodes(ast.AST) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
100
134
|
):
|
|
101
135
|
routes = tuple(route for route in index.routes(function) if not route.is_hidden)
|
|
136
|
+
findings.extend(_direct_app_route_findings(path, routes))
|
|
102
137
|
for route in routes:
|
|
103
138
|
problem = _response_problem(function, route, index)
|
|
104
139
|
if problem is not None:
|
|
@@ -106,6 +141,20 @@ class FastapiClassRouterContract(Rule):
|
|
|
106
141
|
return sorted(findings, key=lambda finding: (finding.line, finding.col, finding.message))
|
|
107
142
|
|
|
108
143
|
|
|
144
|
+
def _direct_app_route_findings(path: Path, routes: tuple[Route, ...]) -> list[Diagnostic]:
|
|
145
|
+
decorators = {route.decorator for route in routes if route.receiver_kind == "FastAPI"}
|
|
146
|
+
return [
|
|
147
|
+
Diagnostic(
|
|
148
|
+
path,
|
|
149
|
+
decorator.lineno,
|
|
150
|
+
decorator.col_offset + 1,
|
|
151
|
+
FastapiClassRouterContract.code,
|
|
152
|
+
"declare HTTP routes on a local APIRouter returned by `*Router.build()`, then compose with include_router",
|
|
153
|
+
)
|
|
154
|
+
for decorator in decorators
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
|
|
109
158
|
def _router_construction_findings(
|
|
110
159
|
path: Path,
|
|
111
160
|
tree: ast.Module,
|
|
@@ -116,7 +165,7 @@ def _router_construction_findings(
|
|
|
116
165
|
) -> list[Diagnostic]:
|
|
117
166
|
findings: list[Diagnostic] = []
|
|
118
167
|
for call in (node for node in walk_ast(tree, index=node_index) if isinstance(node, ast.Call)):
|
|
119
|
-
if index.
|
|
168
|
+
if index.constructor_kind(call) != "APIRouter":
|
|
120
169
|
continue
|
|
121
170
|
function = _ancestor(call, parents, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
122
171
|
cls = _ancestor(call, parents, ast.ClassDef)
|
|
@@ -30,6 +30,8 @@ from sarj_python_lint.rules._paths import is_test_path
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
|
+
from collections.abc import Mapping
|
|
34
|
+
|
|
33
35
|
from sarj_python_lint._file_context import PythonFileContext
|
|
34
36
|
|
|
35
37
|
|
|
@@ -87,6 +89,7 @@ class FastapiExplicitOpenapiContract(Rule):
|
|
|
87
89
|
limitations=(
|
|
88
90
|
"Hidden routes, WebSocket handlers, tests, generated files, documentation-source examples, and unrelated decorators are excluded.",
|
|
89
91
|
"Dynamic response mappings are accepted when their contents cannot be resolved statically.",
|
|
92
|
+
"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.",
|
|
90
93
|
"Explicit status and parameter-location findings are organizational policy even when FastAPI inference would generate valid OpenAPI.",
|
|
91
94
|
(
|
|
92
95
|
"Fixed-record return inference is local and conservative: dynamic keys, opaque escapes, and ambiguous "
|
|
@@ -193,6 +196,44 @@ class FastapiExplicitOpenapiContract(Rule):
|
|
|
193
196
|
scenario="unannotated-record",
|
|
194
197
|
public=True,
|
|
195
198
|
),
|
|
199
|
+
RuleExample(
|
|
200
|
+
example_id="duplicate-operation-id",
|
|
201
|
+
title="Two operations reuse one client method identifier",
|
|
202
|
+
outcome=ExampleOutcome.MATCH,
|
|
203
|
+
files=(
|
|
204
|
+
ExampleFile.python(
|
|
205
|
+
"api.py",
|
|
206
|
+
"from fastapi import APIRouter\n\nrouter = APIRouter()\n\n"
|
|
207
|
+
"@router.get('/items', status_code=200, operation_id='items')\n"
|
|
208
|
+
"async def read_items() -> ItemsResponse: ...\n\n"
|
|
209
|
+
"@router.post('/items', status_code=201, operation_id='items')\n"
|
|
210
|
+
"async def create_item() -> ItemResponse: ...\n",
|
|
211
|
+
),
|
|
212
|
+
),
|
|
213
|
+
focus_path=PurePosixPath("api.py"),
|
|
214
|
+
expected_count=1,
|
|
215
|
+
scenario="operation-id",
|
|
216
|
+
public=True,
|
|
217
|
+
),
|
|
218
|
+
RuleExample(
|
|
219
|
+
example_id="unique-operation-ids",
|
|
220
|
+
title="Each operation has a distinct client method identifier",
|
|
221
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
222
|
+
files=(
|
|
223
|
+
ExampleFile.python(
|
|
224
|
+
"api.py",
|
|
225
|
+
"from fastapi import APIRouter\n\nrouter = APIRouter()\n\n"
|
|
226
|
+
"@router.get('/items', status_code=200, operation_id='read_items')\n"
|
|
227
|
+
"async def read_items() -> ItemsResponse: ...\n\n"
|
|
228
|
+
"@router.post('/items', status_code=201, operation_id='create_item')\n"
|
|
229
|
+
"async def create_item() -> ItemResponse: ...\n",
|
|
230
|
+
),
|
|
231
|
+
),
|
|
232
|
+
focus_path=PurePosixPath("api.py"),
|
|
233
|
+
expected_count=0,
|
|
234
|
+
scenario="operation-id",
|
|
235
|
+
public=True,
|
|
236
|
+
),
|
|
196
237
|
),
|
|
197
238
|
)
|
|
198
239
|
description: str = documentation.summary
|
|
@@ -228,6 +269,7 @@ class FastapiExplicitOpenapiContract(Rule):
|
|
|
228
269
|
findings.extend(_check_projection(route))
|
|
229
270
|
findings.extend(_check_response_statuses(route, index))
|
|
230
271
|
findings.extend(_check_route_conflicts(declared))
|
|
272
|
+
findings.extend(_check_operation_ids(declared, context.parents))
|
|
231
273
|
return [
|
|
232
274
|
Diagnostic(
|
|
233
275
|
path=path,
|
|
@@ -880,6 +922,50 @@ def _check_route_conflicts(routes: list[tuple[int, Route]]) -> list[_Finding]:
|
|
|
880
922
|
return findings
|
|
881
923
|
|
|
882
924
|
|
|
925
|
+
def _check_operation_ids(routes: list[tuple[int, Route]], parents: Mapping[ast.AST, ast.AST]) -> list[_Finding]:
|
|
926
|
+
findings: list[_Finding] = []
|
|
927
|
+
seen: dict[tuple[int, str, str], Route] = {}
|
|
928
|
+
reported: set[int] = set()
|
|
929
|
+
for scope, route in sorted(routes, key=lambda item: item[1].decorator.lineno):
|
|
930
|
+
if (
|
|
931
|
+
route.is_hidden
|
|
932
|
+
or route.path is None
|
|
933
|
+
or route.method == "*"
|
|
934
|
+
or "openapi_extra" in route.keywords
|
|
935
|
+
or _conditional_registration(route.decorator, parents)
|
|
936
|
+
):
|
|
937
|
+
continue
|
|
938
|
+
operation_id = route.keywords.get("operation_id")
|
|
939
|
+
if (
|
|
940
|
+
not isinstance(operation_id, ast.Constant)
|
|
941
|
+
or not isinstance(operation_id.value, str)
|
|
942
|
+
or not operation_id.value
|
|
943
|
+
):
|
|
944
|
+
continue
|
|
945
|
+
key = scope, route.receiver, operation_id.value
|
|
946
|
+
earlier = seen.get(key)
|
|
947
|
+
if earlier is None:
|
|
948
|
+
seen[key] = route
|
|
949
|
+
elif (earlier.path, earlier.method) != (route.path, route.method) and id(route.decorator) not in reported:
|
|
950
|
+
findings.append(
|
|
951
|
+
_Finding(
|
|
952
|
+
operation_id,
|
|
953
|
+
f"[operation-id] duplicate operation_id `{operation_id.value}`; use a unique ID for each operation.",
|
|
954
|
+
)
|
|
955
|
+
)
|
|
956
|
+
reported.add(id(route.decorator))
|
|
957
|
+
return findings
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def _conditional_registration(decorator: ast.Call, parents: Mapping[ast.AST, ast.AST]) -> bool:
|
|
961
|
+
owner = parents.get(decorator)
|
|
962
|
+
while owner is not None:
|
|
963
|
+
if isinstance(owner, (ast.If, ast.Match, ast.Try, ast.TryStar, ast.For, ast.AsyncFor, ast.While)):
|
|
964
|
+
return True
|
|
965
|
+
owner = parents.get(owner)
|
|
966
|
+
return False
|
|
967
|
+
|
|
968
|
+
|
|
883
969
|
def _path_pattern(path: str) -> re.Pattern[str] | None:
|
|
884
970
|
parts: list[str] = []
|
|
885
971
|
position = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_aliases.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_annotations.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_comments.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_declared_tests.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_exhaustiveness.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_parameters.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_repeated.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_security.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/_autofix_sql_projection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_ast_names.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_local_alias_fix.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_route_shapes.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_runtime_bindings.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_static_json_alias.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_store_writes.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/_test_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.76.0 → sarj_python_lint-1.77.0}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|