sarj-python-lint 1.41.2__tar.gz → 1.42.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/PKG-INFO +2 -2
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/pyproject.toml +2 -2
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_annotation_semantics.py +9 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_fastapi.py +4 -5
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +38 -29
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_registry.py +2 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +6 -6
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +6 -6
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +1 -1
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +19 -3
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +4 -6
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +39 -2
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_database_programmability.py +2 -2
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +1 -1
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +1 -1
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +16 -34
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +18 -3
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +13 -11
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_secret_in_log.py +1 -1
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +4 -6
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_class_row.py +1 -1
- sarj_python_lint-1.42.1/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +347 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +10 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +2 -1
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +1 -1
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +1 -1
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +1 -1
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/.gitignore +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/LICENSE +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/README.md +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/unused_mock_setup.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.42.1
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -19,7 +19,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Requires-Dist: packaging==26.3
|
|
21
21
|
Requires-Dist: ruff>=0.17.0
|
|
22
|
-
Requires-Dist: sarj-rule-contracts==0.2.
|
|
22
|
+
Requires-Dist: sarj-rule-contracts==0.2.5
|
|
23
23
|
Requires-Dist: sqlglot==30.22.0
|
|
24
24
|
Requires-Dist: typer==0.27.3
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.42.1"
|
|
4
4
|
description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "sarj-ai" }]
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
"Topic :: Software Development :: Quality Assurance",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["sarj-rule-contracts==0.2.
|
|
18
|
+
dependencies = ["sarj-rule-contracts==0.2.5", "packaging==26.3", "ruff>=0.17.0", "sqlglot==30.22.0", "typer==0.27.3"]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
sarj-python-lint = "sarj_python_lint.__main__:main"
|
|
@@ -131,6 +131,15 @@ def _target_names(node: ast.expr) -> set[str]:
|
|
|
131
131
|
class _ScopeBindings(ast.NodeVisitor):
|
|
132
132
|
def __init__(self) -> None:
|
|
133
133
|
self.names: set[str] = set()
|
|
134
|
+
self._pending: list[ast.AST] = []
|
|
135
|
+
|
|
136
|
+
def visit(self, node: ast.AST) -> None:
|
|
137
|
+
self._pending.append(node)
|
|
138
|
+
while self._pending:
|
|
139
|
+
super().visit(self._pending.pop())
|
|
140
|
+
|
|
141
|
+
def generic_visit(self, node: ast.AST) -> None:
|
|
142
|
+
self._pending.extend(reversed(tuple(ast.iter_child_nodes(node))))
|
|
134
143
|
|
|
135
144
|
def visit_Name(self, node: ast.Name) -> None:
|
|
136
145
|
if isinstance(node.ctx, (ast.Store, ast.Del)):
|
|
@@ -138,7 +138,9 @@ class FastapiIndex:
|
|
|
138
138
|
self._read_aliases(tree)
|
|
139
139
|
|
|
140
140
|
def _index_scopes(self, tree: ast.Module) -> None:
|
|
141
|
-
|
|
141
|
+
pending: list[tuple[ast.AST, int, int | None]] = [(tree, 0, None)]
|
|
142
|
+
while pending:
|
|
143
|
+
node, scope, class_owner = pending.pop()
|
|
142
144
|
self._node_scopes[id(node)] = scope
|
|
143
145
|
if isinstance(node, ast.ClassDef):
|
|
144
146
|
class_owner = node.lineno
|
|
@@ -149,10 +151,7 @@ class FastapiIndex:
|
|
|
149
151
|
self._route_scopes[id(node)] = scope
|
|
150
152
|
self._scope_parents[node.lineno] = scope
|
|
151
153
|
scope = node.lineno
|
|
152
|
-
for child in ast.iter_child_nodes(node)
|
|
153
|
-
visit(child, scope, class_owner)
|
|
154
|
-
|
|
155
|
-
visit(tree, 0, None)
|
|
154
|
+
pending.extend((child, scope, class_owner) for child in reversed(tuple(ast.iter_child_nodes(node))))
|
|
156
155
|
|
|
157
156
|
def _read_imports(self, tree: ast.Module) -> None:
|
|
158
157
|
for node in walk_ast(tree):
|
|
@@ -108,39 +108,48 @@ def inert_signature(node: ast.FunctionDef | ast.AsyncFunctionDef, context: Pytho
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def _inert_expr(node: ast.AST, context: PythonFileContext) -> bool:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"pydantic.config.ConfigDict",
|
|
122
|
-
}:
|
|
111
|
+
pending = [node]
|
|
112
|
+
while pending:
|
|
113
|
+
current = pending.pop()
|
|
114
|
+
if isinstance(current, ast.Subscript) and not _inert_subscription(current, context):
|
|
115
|
+
return False
|
|
116
|
+
if isinstance(current, ast.Lambda):
|
|
117
|
+
if current.args.defaults or current.args.kw_defaults:
|
|
118
|
+
return False
|
|
119
|
+
continue
|
|
120
|
+
if isinstance(current, ast.Call) and not _inert_pydantic_call(current, context):
|
|
123
121
|
return False
|
|
124
|
-
if
|
|
125
|
-
|
|
122
|
+
if isinstance(
|
|
123
|
+
current,
|
|
124
|
+
(
|
|
125
|
+
ast.NamedExpr,
|
|
126
|
+
ast.Starred,
|
|
127
|
+
ast.Await,
|
|
128
|
+
ast.Yield,
|
|
129
|
+
ast.YieldFrom,
|
|
130
|
+
ast.ListComp,
|
|
131
|
+
ast.SetComp,
|
|
132
|
+
ast.DictComp,
|
|
133
|
+
ast.GeneratorExp,
|
|
134
|
+
),
|
|
126
135
|
):
|
|
127
136
|
return False
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
),
|
|
141
|
-
):
|
|
137
|
+
pending.extend(reversed(tuple(ast.iter_child_nodes(current))))
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _inert_pydantic_call(node: ast.Call, context: PythonFileContext) -> bool:
|
|
142
|
+
target = context.imports.resolved_qualified_name(node.func)
|
|
143
|
+
if target not in {
|
|
144
|
+
"pydantic.Field",
|
|
145
|
+
"pydantic.fields.Field",
|
|
146
|
+
"pydantic.ConfigDict",
|
|
147
|
+
"pydantic.config.ConfigDict",
|
|
148
|
+
}:
|
|
142
149
|
return False
|
|
143
|
-
return
|
|
150
|
+
return not any(keyword.arg is None for keyword in node.keywords) and not any(
|
|
151
|
+
isinstance(argument, ast.Starred) for argument in node.args
|
|
152
|
+
)
|
|
144
153
|
|
|
145
154
|
|
|
146
155
|
def _inert_subscription(node: ast.Subscript, context: PythonFileContext) -> bool:
|
|
@@ -216,6 +216,7 @@ from sarj_python_lint.rules.duplicate_closed_pure_helper import DuplicateClosedP
|
|
|
216
216
|
from sarj_python_lint.rules.no_conflicting_pydantic_alias_validation import NoConflictingPydanticAliasValidation
|
|
217
217
|
from sarj_python_lint.rules.no_impossible_pydantic_field_bounds import NoImpossiblePydanticFieldBounds
|
|
218
218
|
from sarj_python_lint.rules.no_unknown_pydantic_validator_field import NoUnknownPydanticValidatorField
|
|
219
|
+
from sarj_python_lint.rules.prefer_discriminated_model_union import PreferDiscriminatedModelUnion
|
|
219
220
|
from sarj_python_lint.rules.redundant_test_constructor_forwarder import RedundantTestConstructorForwarder
|
|
220
221
|
from sarj_python_lint.rules.repeated_unimplemented_test_contract import RepeatedUnimplementedTestContract
|
|
221
222
|
from sarj_python_lint.rules.test_without_explicit_oracle import NoExplicitTestOracle
|
|
@@ -224,6 +225,7 @@ from sarj_python_lint.rules.test_without_explicit_oracle import NoExplicitTestOr
|
|
|
224
225
|
REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
225
226
|
{
|
|
226
227
|
NoExplicitTestOracle.id: NoExplicitTestOracle,
|
|
228
|
+
PreferDiscriminatedModelUnion.id: PreferDiscriminatedModelUnion,
|
|
227
229
|
NoConflictingPydanticAliasValidation.id: NoConflictingPydanticAliasValidation,
|
|
228
230
|
NoUnknownPydanticValidatorField.id: NoUnknownPydanticValidatorField,
|
|
229
231
|
NoImpossiblePydanticFieldBounds.id: NoImpossiblePydanticFieldBounds,
|
|
@@ -128,14 +128,14 @@ class DocstringArgsRestateSignature(Rule):
|
|
|
128
128
|
imports: ImportIndex,
|
|
129
129
|
diags: list[Diagnostic],
|
|
130
130
|
) -> None:
|
|
131
|
-
for child in children(node)
|
|
131
|
+
pending: list[tuple[ast.AST, str | None]] = [(child, class_name) for child in reversed(children(node))]
|
|
132
|
+
while pending:
|
|
133
|
+
child, owner = pending.pop()
|
|
132
134
|
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
133
|
-
self._check_function(child,
|
|
134
|
-
self._walk(child, class_name, path, imports, diags)
|
|
135
|
+
self._check_function(child, owner, path, imports, diags)
|
|
135
136
|
elif isinstance(child, ast.ClassDef):
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self._walk(child, class_name, path, imports, diags)
|
|
137
|
+
owner = child.name
|
|
138
|
+
pending.extend((nested, owner) for nested in reversed(children(child)))
|
|
139
139
|
|
|
140
140
|
def _check_function(
|
|
141
141
|
self,
|
|
@@ -196,14 +196,14 @@ class DocstringReturnsRestateSignature(Rule):
|
|
|
196
196
|
imports: ImportIndex,
|
|
197
197
|
diags: list[Diagnostic],
|
|
198
198
|
) -> None:
|
|
199
|
-
for child in children(node)
|
|
199
|
+
pending: list[tuple[ast.AST, str | None]] = [(child, class_name) for child in reversed(children(node))]
|
|
200
|
+
while pending:
|
|
201
|
+
child, owner = pending.pop()
|
|
200
202
|
if isinstance(child, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
201
|
-
self._check_function(child,
|
|
202
|
-
self._walk(child, class_name, path, imports, diags)
|
|
203
|
+
self._check_function(child, owner, path, imports, diags)
|
|
203
204
|
elif isinstance(child, ast.ClassDef):
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
self._walk(child, class_name, path, imports, diags)
|
|
205
|
+
owner = child.name
|
|
206
|
+
pending.extend((nested, owner) for nested in reversed(children(child)))
|
|
207
207
|
|
|
208
208
|
def _check_function(
|
|
209
209
|
self,
|
|
@@ -62,7 +62,7 @@ class IacSourceCoupledTest(Rule):
|
|
|
62
62
|
files=(
|
|
63
63
|
ExampleFile.python(
|
|
64
64
|
"tests/test_policy.py",
|
|
65
|
-
"
|
|
65
|
+
"import json\n\ndef test_policy(rendered_plan_json: str):\n"
|
|
66
66
|
" plan = json.loads(rendered_plan_json)\n"
|
|
67
67
|
" changes = plan['resource_changes']\n"
|
|
68
68
|
" assert changes[0]['change']['actions'] == ['create']\n",
|
|
@@ -314,12 +314,24 @@ def _literal_key(value: object) -> _LiteralKey:
|
|
|
314
314
|
return _LiteralKey(type(value), value)
|
|
315
315
|
|
|
316
316
|
|
|
317
|
+
def _bit_union_members(node: ast.expr) -> list[ast.expr]:
|
|
318
|
+
members: list[ast.expr] = []
|
|
319
|
+
pending = [node]
|
|
320
|
+
while pending:
|
|
321
|
+
member = pending.pop()
|
|
322
|
+
if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
323
|
+
pending.extend((member.right, member.left))
|
|
324
|
+
else:
|
|
325
|
+
members.append(member)
|
|
326
|
+
return members
|
|
327
|
+
|
|
328
|
+
|
|
317
329
|
def _literal_domain(node: ast.expr, imports: ImportIndex) -> _LiteralDomain | None:
|
|
318
330
|
node = _parse_forward_annotation(node)
|
|
319
331
|
if isinstance(node, ast.Constant) and node.value is None:
|
|
320
332
|
return _LiteralDomain(frozenset({_literal_key(None)}))
|
|
321
333
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
322
|
-
return _merge_domains((_literal_domain(
|
|
334
|
+
return _merge_domains(tuple(_literal_domain(member, imports) for member in _bit_union_members(node)))
|
|
323
335
|
if not isinstance(node, ast.Subscript):
|
|
324
336
|
return None
|
|
325
337
|
if imports.resolves(node.value, sources=_TYPING_SOURCES, symbol="Annotated"):
|
|
@@ -334,8 +346,12 @@ def _literal_domain(node: ast.expr, imports: ImportIndex) -> _LiteralDomain | No
|
|
|
334
346
|
return _merge_domains(tuple(_literal_domain(member, imports) for member in _slice_members(node.slice)))
|
|
335
347
|
if not imports.resolves(node.value, sources=_TYPING_SOURCES, symbol="Literal"):
|
|
336
348
|
return None
|
|
349
|
+
return _literal_members_domain(_slice_members(node.slice))
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _literal_members_domain(members: tuple[ast.expr, ...]) -> _LiteralDomain | None:
|
|
337
353
|
values: set[_LiteralKey] = set()
|
|
338
|
-
for member in
|
|
354
|
+
for member in members:
|
|
339
355
|
value = _literal_value(member)
|
|
340
356
|
if value is _MISSING or isinstance(value, float):
|
|
341
357
|
return None
|
|
@@ -514,7 +530,7 @@ def _numeric_bound_is_violated(name: str, default: float, bound: float) -> bool:
|
|
|
514
530
|
def _provably_non_null(node: ast.expr, imports: ImportIndex) -> bool:
|
|
515
531
|
node = _parse_forward_annotation(node)
|
|
516
532
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
517
|
-
return _provably_non_null(
|
|
533
|
+
return all(_provably_non_null(member, imports) for member in _bit_union_members(node))
|
|
518
534
|
if isinstance(node, ast.Name):
|
|
519
535
|
return node.id in _KNOWN_NON_NULL_BUILTINS and imports.builtin_is_unshadowed(node.id)
|
|
520
536
|
if not isinstance(node, ast.Subscript):
|
|
@@ -237,16 +237,14 @@ def _is_framework_hook(node: ast.FunctionDef | ast.AsyncFunctionDef, imports: Im
|
|
|
237
237
|
|
|
238
238
|
def _private_class_function_ids(tree: ast.Module) -> set[int]:
|
|
239
239
|
private: set[int] = set()
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
pending: list[tuple[ast.AST, bool]] = [(tree, False)]
|
|
241
|
+
while pending:
|
|
242
|
+
node, inside_private_class = pending.pop()
|
|
242
243
|
if isinstance(node, ast.ClassDef):
|
|
243
244
|
inside_private_class = inside_private_class or node.name.startswith("_")
|
|
244
245
|
if inside_private_class and isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
245
246
|
private.add(id(node))
|
|
246
|
-
for child in children(node)
|
|
247
|
-
visit(child, inside_private_class=inside_private_class)
|
|
248
|
-
|
|
249
|
-
visit(tree, inside_private_class=False)
|
|
247
|
+
pending.extend((child, inside_private_class) for child in reversed(children(node)))
|
|
250
248
|
return private
|
|
251
249
|
|
|
252
250
|
|
|
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from dataclasses import replace
|
|
5
|
+
from io import StringIO
|
|
5
6
|
from pathlib import PurePosixPath
|
|
7
|
+
import tokenize
|
|
6
8
|
from types import MappingProxyType
|
|
7
9
|
from typing import TYPE_CHECKING, ClassVar, final
|
|
8
10
|
|
|
@@ -187,8 +189,10 @@ class NoBroadKeywordCapture(Rule):
|
|
|
187
189
|
overloads = _overload_implementations(context, semantics, mutated_members)
|
|
188
190
|
if function in overloads:
|
|
189
191
|
continue
|
|
190
|
-
if
|
|
191
|
-
context.source_lines,
|
|
192
|
+
if (
|
|
193
|
+
is_suppressed(context.source_lines, argument.lineno, self.code)
|
|
194
|
+
or is_suppressed(context.source_lines, function.lineno, self.code)
|
|
195
|
+
or _signature_end_suppressed(function, context, self.code)
|
|
192
196
|
):
|
|
193
197
|
continue
|
|
194
198
|
findings.append(
|
|
@@ -207,6 +211,39 @@ class NoBroadKeywordCapture(Rule):
|
|
|
207
211
|
return sorted(findings, key=lambda finding: (finding.line, finding.col))
|
|
208
212
|
|
|
209
213
|
|
|
214
|
+
def _signature_end_suppressed(
|
|
215
|
+
function: ast.FunctionDef | ast.AsyncFunctionDef, context: PythonFileContext, code: str
|
|
216
|
+
) -> bool:
|
|
217
|
+
first_statement = function.body[0]
|
|
218
|
+
header = context.source_lines[function.lineno - 1 : first_statement.lineno]
|
|
219
|
+
if not any("sarj-noqa" in line.lower() for line in header):
|
|
220
|
+
return False
|
|
221
|
+
# AST columns count UTF-8 bytes, and a continued inline body can share the header's logical line.
|
|
222
|
+
header[-1] = header[-1].encode("utf-8")[: first_statement.col_offset].decode("utf-8")
|
|
223
|
+
try:
|
|
224
|
+
line = _signature_end_line(header, function.lineno)
|
|
225
|
+
except tokenize.TokenError, SyntaxError:
|
|
226
|
+
return False
|
|
227
|
+
return line is not None and is_suppressed(context.source_lines, line, code)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _signature_end_line(header: list[str], start_line: int) -> int | None:
|
|
231
|
+
depth = 0
|
|
232
|
+
line = None
|
|
233
|
+
for token in tokenize.generate_tokens(StringIO("\n".join(header)).readline):
|
|
234
|
+
if token.type == tokenize.NEWLINE:
|
|
235
|
+
return line
|
|
236
|
+
if token.type != tokenize.OP:
|
|
237
|
+
continue
|
|
238
|
+
if token.string in {"(", "[", "{"}:
|
|
239
|
+
depth += 1
|
|
240
|
+
elif token.string in {")", "]", "}"}:
|
|
241
|
+
depth -= 1
|
|
242
|
+
elif token.string == ":" and depth == 0:
|
|
243
|
+
line = start_line + token.start[0] - 1
|
|
244
|
+
return line
|
|
245
|
+
|
|
246
|
+
|
|
210
247
|
def _interface_severity(
|
|
211
248
|
function: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
212
249
|
context: PythonFileContext,
|
|
@@ -29,8 +29,8 @@ _DATABASE_BEHAVIOR = re.compile(
|
|
|
29
29
|
r"CREATE\s+CONSTRAINT\s+TRIGGER|ALTER\s+TABLE\s+[^;]+?\s+ENABLE\s+(?:(?:ALWAYS|REPLICA)\s+)?TRIGGER)\b",
|
|
30
30
|
re.IGNORECASE,
|
|
31
31
|
)
|
|
32
|
-
_BAD = 'await conn.execute("CREATE TRIGGER fail_counter BEFORE UPDATE ON batch EXECUTE FUNCTION fail_counter()")\n'
|
|
33
|
-
_GOOD = 'await conn.execute("ALTER TABLE batch ADD CONSTRAINT fail_counter CHECK (false) NOT VALID")\n'
|
|
32
|
+
_BAD = 'async def install_fault(conn):\n await conn.execute("CREATE TRIGGER fail_counter BEFORE UPDATE ON batch EXECUTE FUNCTION fail_counter()")\n'
|
|
33
|
+
_GOOD = 'async def install_fault(conn):\n await conn.execute("ALTER TABLE batch ADD CONSTRAINT fail_counter CHECK (false) NOT VALID")\n'
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
@final
|
{sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py
RENAMED
|
@@ -83,7 +83,7 @@ class NoLoguruExcInfo(Rule):
|
|
|
83
83
|
|
|
84
84
|
@override
|
|
85
85
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
86
|
-
if "exc_info" not in context.
|
|
86
|
+
if "exc_info" not in context.symbol_source or context.generated or context.tree is None:
|
|
87
87
|
return []
|
|
88
88
|
shadowed = _ambiguous_import_names(context)
|
|
89
89
|
if "*" in shadowed:
|
|
@@ -89,7 +89,7 @@ class NoLoguruPrintfFormat(Rule):
|
|
|
89
89
|
|
|
90
90
|
@override
|
|
91
91
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
92
|
-
if "loguru" not in context.
|
|
92
|
+
if "loguru" not in context.symbol_source or context.generated or context.tree is None:
|
|
93
93
|
return []
|
|
94
94
|
provenance: ResourceProvenance | None = None
|
|
95
95
|
diagnostics: list[Diagnostic] = []
|
|
@@ -386,40 +386,22 @@ def _has_dynamic_reflective_self_write(function: ast.FunctionDef | ast.AsyncFunc
|
|
|
386
386
|
|
|
387
387
|
def _function_scopes(tree: ast.Module) -> list[_FunctionScope]:
|
|
388
388
|
scopes: list[_FunctionScope] = []
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
@override
|
|
407
|
-
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
408
|
-
self._function(node)
|
|
409
|
-
|
|
410
|
-
def _function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
|
411
|
-
scopes.append(_FunctionScope(node, self.owner))
|
|
412
|
-
previous = self.owner
|
|
413
|
-
self.owner = None
|
|
414
|
-
for statement in node.body:
|
|
415
|
-
self.visit(statement)
|
|
416
|
-
self.owner = previous
|
|
417
|
-
|
|
418
|
-
@override
|
|
419
|
-
def visit_Lambda(self, node: ast.Lambda) -> None:
|
|
420
|
-
return
|
|
421
|
-
|
|
422
|
-
Collector().visit(tree)
|
|
389
|
+
pending: list[tuple[ast.AST, ast.ClassDef | None]] = [(tree, None)]
|
|
390
|
+
while pending:
|
|
391
|
+
node, owner = pending.pop()
|
|
392
|
+
match node:
|
|
393
|
+
case ast.Lambda():
|
|
394
|
+
continue
|
|
395
|
+
case ast.ClassDef():
|
|
396
|
+
owner = node
|
|
397
|
+
children = tuple(node.body)
|
|
398
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef():
|
|
399
|
+
scopes.append(_FunctionScope(node, owner))
|
|
400
|
+
owner = None
|
|
401
|
+
children = tuple(node.body)
|
|
402
|
+
case _:
|
|
403
|
+
children = tuple(ast.iter_child_nodes(node))
|
|
404
|
+
pending.extend((child, owner) for child in reversed(children))
|
|
423
405
|
return scopes
|
|
424
406
|
|
|
425
407
|
|
|
@@ -275,9 +275,7 @@ def _closed_domain(
|
|
|
275
275
|
if isinstance(node, ast.Name):
|
|
276
276
|
return local_domains.get(node.id)
|
|
277
277
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
278
|
-
|
|
279
|
-
right = _closed_domain(node.right, imports, local_domains)
|
|
280
|
-
return left | right if left is not None and right is not None else None
|
|
278
|
+
return _closed_union_domain(node, imports, local_domains)
|
|
281
279
|
if not isinstance(node, ast.Subscript):
|
|
282
280
|
return None
|
|
283
281
|
if imports.resolves(node.value, sources=_TYPING_SOURCES, symbol="Literal"):
|
|
@@ -288,6 +286,23 @@ def _closed_domain(
|
|
|
288
286
|
return None
|
|
289
287
|
|
|
290
288
|
|
|
289
|
+
def _closed_union_domain(
|
|
290
|
+
node: ast.expr, imports: ImportIndex, local_domains: dict[str, frozenset[str]]
|
|
291
|
+
) -> frozenset[str] | None:
|
|
292
|
+
values: set[str] = set()
|
|
293
|
+
pending = [node]
|
|
294
|
+
while pending:
|
|
295
|
+
member = pending.pop()
|
|
296
|
+
if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
297
|
+
pending.extend((member.right, member.left))
|
|
298
|
+
continue
|
|
299
|
+
domain = _closed_domain(member, imports, local_domains)
|
|
300
|
+
if domain is None:
|
|
301
|
+
return None
|
|
302
|
+
values.update(domain)
|
|
303
|
+
return frozenset(values)
|
|
304
|
+
|
|
305
|
+
|
|
291
306
|
def _repeats_domain(description: str, domain: frozenset[str]) -> bool:
|
|
292
307
|
match = _DOMAIN_CLAUSE.search(description)
|
|
293
308
|
if match is None:
|
|
@@ -154,17 +154,19 @@ class NoRepeatedStringLiteral(Rule):
|
|
|
154
154
|
scope_of[id(node)] = scope
|
|
155
155
|
|
|
156
156
|
def visit(node: ast.AST, scope: int) -> None:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
pending = [(node, scope)]
|
|
158
|
+
while pending:
|
|
159
|
+
node, scope = pending.pop()
|
|
160
|
+
for annotation in _annotation_exprs(node):
|
|
161
|
+
excluded.update(id(child) for child in walk(annotation) if isinstance(child, ast.Constant))
|
|
162
|
+
if isinstance(node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
163
|
+
scope = declaration_scope(node, scope)
|
|
164
|
+
elif isinstance(node, ast.Lambda):
|
|
165
|
+
scope = id(node)
|
|
166
|
+
scope_line_of[scope] = node.lineno
|
|
167
|
+
else:
|
|
168
|
+
collect_literal(node, scope)
|
|
169
|
+
pending.extend((child, scope) for child in reversed(children(node)))
|
|
168
170
|
|
|
169
171
|
visit(tree, _MODULE_SCOPE)
|
|
170
172
|
|
{sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
@@ -103,7 +103,7 @@ class NoSecretInLog(Rule):
|
|
|
103
103
|
),
|
|
104
104
|
RuleExample(
|
|
105
105
|
example_id="redacted-logging-keyword",
|
|
106
|
-
title="
|
|
106
|
+
title="Only non-sensitive authentication metadata is logged",
|
|
107
107
|
outcome=ExampleOutcome.NO_MATCH,
|
|
108
108
|
files=(
|
|
109
109
|
ExampleFile.python(
|
|
@@ -218,15 +218,13 @@ def _is_syntax_required(owner: ast.Module | ast.ClassDef | ast.FunctionDef | ast
|
|
|
218
218
|
|
|
219
219
|
def _owner_keys(tree: ast.Module) -> dict[int, str]:
|
|
220
220
|
keys = {id(tree): "__doc__"}
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
pending: list[tuple[ast.AST, tuple[str, ...]]] = [(tree, ())]
|
|
222
|
+
while pending:
|
|
223
|
+
node, prefix = pending.pop()
|
|
223
224
|
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
224
225
|
prefix = (*prefix, node.name)
|
|
225
226
|
keys[id(node)] = ".".join(prefix)
|
|
226
|
-
for child in ast.iter_child_nodes(node)
|
|
227
|
-
visit(child, prefix)
|
|
228
|
-
|
|
229
|
-
visit(tree, ())
|
|
227
|
+
pending.extend((child, prefix) for child in reversed(tuple(ast.iter_child_nodes(node))))
|
|
230
228
|
return keys
|
|
231
229
|
|
|
232
230
|
|
{sarj_python_lint-1.41.2 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_class_row.py
RENAMED
|
@@ -90,7 +90,7 @@ class PreferClassRow(Rule):
|
|
|
90
90
|
files=(
|
|
91
91
|
ExampleFile.python(
|
|
92
92
|
"app/task_store.py",
|
|
93
|
-
|
|
93
|
+
'from psycopg.rows import class_row\n\nasync def load(conn):\n async with conn.cursor(row_factory=class_row(Task)) as cursor:\n await cursor.execute("SELECT id, state FROM task")\n return await cursor.fetchone()\n',
|
|
94
94
|
),
|
|
95
95
|
),
|
|
96
96
|
focus_path=PurePosixPath("app/task_store.py"),
|