sarj-python-lint 1.7.0__tar.gz → 1.7.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.7.0 → sarj_python_lint-1.7.1}/PKG-INFO +1 -1
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/pyproject.toml +1 -1
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +353 -63
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/.gitignore +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/LICENSE +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/README.md +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.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/
|
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from collections import Counter
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass, field, replace
|
|
6
7
|
from pathlib import PurePosixPath
|
|
7
8
|
from typing import TYPE_CHECKING, ClassVar, NamedTuple, final, override
|
|
8
9
|
|
|
@@ -71,6 +72,9 @@ class _WrapperSummary:
|
|
|
71
72
|
class _WrapperFactorySummary:
|
|
72
73
|
wrapper: _WrapperSummary
|
|
73
74
|
accessor_origins: dict[str, frozenset[ast.Call]]
|
|
75
|
+
accessor_parameters: dict[str, frozenset[str]]
|
|
76
|
+
signature: ast.arguments
|
|
77
|
+
narrowing_helpers: frozenset[str]
|
|
74
78
|
|
|
75
79
|
|
|
76
80
|
@dataclass(frozen=True, slots=True)
|
|
@@ -130,7 +134,7 @@ class RequirePydanticForExternalJson(Rule):
|
|
|
130
134
|
limitations=(
|
|
131
135
|
"The rule follows import-proven JSON decoders, requests/httpx/aiohttp responses and clients, environment or subprocess results, and simple module-local helpers through single-assignment names.",
|
|
132
136
|
"Simple same-module wrappers preserve constructor arguments through direct stored-field accessor methods, including local identity-or-empty-dict narrowing helpers; inheritance, decorators, mutation, nested captures, unknown member escapes, and unknown helpers are excluded.",
|
|
133
|
-
"
|
|
137
|
+
"Synchronous module factories preserve direct wrapper returns, single-assignment local aliases, and required positional or keyword parameters substituted from proven call-site inputs. Defaults, variadics, branches, unknown effects or transforms, async/method/decorated factories, captures, and factory chains are excluded; annotations alone do not establish factory provenance.",
|
|
134
138
|
"It diagnoses literal-key subscription and get calls; iteration and other dynamic JSON use remain out of scope.",
|
|
135
139
|
"Unannotated parameters and unknown expressions are not assumed to be external; interprocedural and framework-specific boundaries can remain unreported.",
|
|
136
140
|
"Names bound by loops, comprehensions, context managers, exception handlers, pattern matching, imports, or destructuring are excluded when their provenance is ambiguous; unrelated binders do not suppress the rest of a function.",
|
|
@@ -151,6 +155,36 @@ class RequirePydanticForExternalJson(Rule):
|
|
|
151
155
|
expected_count=1,
|
|
152
156
|
public=True,
|
|
153
157
|
),
|
|
158
|
+
RuleExample(
|
|
159
|
+
example_id="factory-forwarded-external-json",
|
|
160
|
+
scenario="factory-forwarding",
|
|
161
|
+
title="A local factory forwards an unvalidated response",
|
|
162
|
+
outcome=ExampleOutcome.MATCH,
|
|
163
|
+
files=(
|
|
164
|
+
ExampleFile.python(
|
|
165
|
+
"protocol.py",
|
|
166
|
+
"import httpx\n\nclass Envelope:\n def __init__(self, body):\n self.body = body\n def payload(self):\n return self.body\n\ndef wrap(body):\n result = Envelope(body)\n return result\n\ndef fetch():\n raw = httpx.get('https://api.example/report').json()\n return wrap(raw).payload()['version']\n",
|
|
167
|
+
),
|
|
168
|
+
),
|
|
169
|
+
focus_path=PurePosixPath("protocol.py"),
|
|
170
|
+
expected_count=1,
|
|
171
|
+
public=True,
|
|
172
|
+
),
|
|
173
|
+
RuleExample(
|
|
174
|
+
example_id="factory-local-record",
|
|
175
|
+
scenario="factory-forwarding",
|
|
176
|
+
title="A factory receives a locally constructed record",
|
|
177
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
178
|
+
files=(
|
|
179
|
+
ExampleFile.python(
|
|
180
|
+
"protocol.py",
|
|
181
|
+
"import httpx\n\nclass Envelope:\n def __init__(self, body):\n self.body = body\n def payload(self):\n return self.body\n\ndef wrap(body):\n return Envelope(body)\n\ndef fetch():\n remote = httpx.get('https://api.example/report').json()\n return wrap({'version': 1}).payload()['version']\n",
|
|
182
|
+
),
|
|
183
|
+
),
|
|
184
|
+
focus_path=PurePosixPath("protocol.py"),
|
|
185
|
+
expected_count=0,
|
|
186
|
+
public=True,
|
|
187
|
+
),
|
|
154
188
|
RuleExample(
|
|
155
189
|
example_id="pydantic-external-json-validation",
|
|
156
190
|
title="External JSON validated by a boundary model",
|
|
@@ -550,6 +584,7 @@ def _function_findings(
|
|
|
550
584
|
bindings = _unique_bindings(scope, excluded_names=_ambiguous_binding_names(scope))
|
|
551
585
|
local_bound_names = _function_local_bound_names(function, scope)
|
|
552
586
|
response_names = _http_response_names(scope, imports, summaries, function, shadowed_names=local_bound_names)
|
|
587
|
+
validated_names = _validated_names(function, imports, summaries.jsonschema_validator_names)
|
|
553
588
|
resolver = _OriginResolver(
|
|
554
589
|
imports,
|
|
555
590
|
summaries,
|
|
@@ -559,8 +594,8 @@ def _function_findings(
|
|
|
559
594
|
bindings,
|
|
560
595
|
local_bound_names,
|
|
561
596
|
scope,
|
|
597
|
+
validated_names,
|
|
562
598
|
)
|
|
563
|
-
validated_names = _validated_names(function, imports, summaries.jsonschema_validator_names)
|
|
564
599
|
findings: list[_ExternalRecordFinding] = []
|
|
565
600
|
for node in scope:
|
|
566
601
|
access = _record_access(node)
|
|
@@ -770,6 +805,7 @@ class _OriginResolver:
|
|
|
770
805
|
bindings: dict[str, ast.expr]
|
|
771
806
|
local_bound_names: frozenset[str]
|
|
772
807
|
scope: tuple[ast.AST, ...]
|
|
808
|
+
validated_names: _ValidationHistory = field(default_factory=dict)
|
|
773
809
|
|
|
774
810
|
def origins(self, expression: ast.expr, resolving: frozenset[str] = frozenset()) -> frozenset[ast.Call]:
|
|
775
811
|
expression = _unwrap_await(expression)
|
|
@@ -812,30 +848,94 @@ class _OriginResolver:
|
|
|
812
848
|
found_origins.update(self.origins(keyword.value, resolving))
|
|
813
849
|
return frozenset(found_origins)
|
|
814
850
|
|
|
815
|
-
def
|
|
851
|
+
def factory_call_is_known(self, call: ast.Call) -> bool:
|
|
852
|
+
if self._is_validation_call(call):
|
|
853
|
+
return True
|
|
854
|
+
if not self._is_source(call):
|
|
855
|
+
return False
|
|
856
|
+
if (
|
|
857
|
+
isinstance(call.func, ast.Attribute)
|
|
858
|
+
and isinstance(call.func.value, ast.Name)
|
|
859
|
+
and call.func.attr == "json"
|
|
860
|
+
and call.func.value.id in self.response_names
|
|
861
|
+
):
|
|
862
|
+
return True
|
|
863
|
+
receiver = call.func
|
|
864
|
+
while isinstance(receiver, ast.Attribute):
|
|
865
|
+
receiver = receiver.value
|
|
866
|
+
return not isinstance(receiver, ast.Name) or receiver.id not in self.local_bound_names
|
|
867
|
+
|
|
868
|
+
def factory_argument_origins(
|
|
869
|
+
self,
|
|
870
|
+
value: ast.expr,
|
|
871
|
+
narrowing_helpers: frozenset[str],
|
|
872
|
+
*,
|
|
873
|
+
use: ast.expr | None = None,
|
|
874
|
+
resolving: frozenset[str] = frozenset(),
|
|
875
|
+
) -> frozenset[ast.Call]:
|
|
876
|
+
if isinstance(value, ast.Name):
|
|
877
|
+
if use is not None and _was_validated_before(value, use, self.validated_names):
|
|
878
|
+
return frozenset()
|
|
879
|
+
binding = self.bindings.get(value.id)
|
|
880
|
+
if binding is None or value.id in resolving or _node_position(binding) >= _node_position(value):
|
|
881
|
+
return frozenset()
|
|
882
|
+
return self.factory_argument_origins(binding, narrowing_helpers, use=use, resolving=resolving | {value.id})
|
|
816
883
|
if not isinstance(value, ast.Call) or self._is_validation_call(value):
|
|
817
884
|
return frozenset()
|
|
818
885
|
if self._is_source(value):
|
|
819
886
|
return frozenset({value})
|
|
820
|
-
if (
|
|
821
|
-
|
|
822
|
-
and value.func.id in narrowing_helpers
|
|
823
|
-
and len(value.args) == 1
|
|
824
|
-
and not value.keywords
|
|
825
|
-
):
|
|
826
|
-
return self.factory_argument_origins(value.args[0], narrowing_helpers)
|
|
887
|
+
if _is_narrowing_call(value, narrowing_helpers, self.local_bound_names):
|
|
888
|
+
return self.factory_argument_origins(value.args[0], narrowing_helpers, use=use, resolving=resolving)
|
|
827
889
|
return frozenset()
|
|
828
890
|
|
|
829
891
|
def _factory_accessor_origins(self, access: ast.Attribute) -> frozenset[ast.Call] | None:
|
|
830
892
|
receiver = self._resolved_binding(access.value) if isinstance(access.value, ast.Name) else access.value
|
|
831
893
|
if not isinstance(receiver, ast.Call) or not isinstance(receiver.func, ast.Name):
|
|
832
894
|
return None
|
|
833
|
-
if receiver.
|
|
895
|
+
if receiver.func.id in self.local_bound_names:
|
|
834
896
|
return None
|
|
835
897
|
factory = self.summaries.wrapper_factories.get(receiver.func.id)
|
|
836
898
|
if factory is None or not self._wrapper_is_stable(receiver, factory.wrapper):
|
|
837
899
|
return None
|
|
838
|
-
|
|
900
|
+
arguments = _factory_call_arguments(receiver, factory.signature)
|
|
901
|
+
if arguments is None:
|
|
902
|
+
return frozenset()
|
|
903
|
+
origins = set(factory.accessor_origins.get(access.attr, ()))
|
|
904
|
+
for parameter in factory.accessor_parameters.get(access.attr, ()):
|
|
905
|
+
value = arguments[parameter]
|
|
906
|
+
if self._factory_argument_is_stable(value, receiver, factory.narrowing_helpers):
|
|
907
|
+
origins.update(self.factory_argument_origins(value, factory.narrowing_helpers, use=access))
|
|
908
|
+
return frozenset(origins)
|
|
909
|
+
|
|
910
|
+
def _factory_argument_is_stable(
|
|
911
|
+
self, value: ast.expr, factory_call: ast.Call, narrowing_helpers: frozenset[str]
|
|
912
|
+
) -> bool:
|
|
913
|
+
if not isinstance(value, ast.Name):
|
|
914
|
+
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id in narrowing_helpers:
|
|
915
|
+
return len(value.args) == 1 and self._factory_argument_is_stable(
|
|
916
|
+
value.args[0], factory_call, narrowing_helpers
|
|
917
|
+
)
|
|
918
|
+
return True
|
|
919
|
+
original = self._resolved_binding(value)
|
|
920
|
+
|
|
921
|
+
def refers_to_payload(node: ast.AST) -> bool:
|
|
922
|
+
return isinstance(node, ast.Name) and self._resolved_binding(node) is original
|
|
923
|
+
|
|
924
|
+
def contains_payload(node: ast.AST) -> bool:
|
|
925
|
+
if node is factory_call:
|
|
926
|
+
return False
|
|
927
|
+
return refers_to_payload(node) or any(contains_payload(child) for child in ast.iter_child_nodes(node))
|
|
928
|
+
|
|
929
|
+
for node in self.scope:
|
|
930
|
+
if _reference_escapes(node, contains_payload):
|
|
931
|
+
return False
|
|
932
|
+
if not isinstance(node, ast.Call) or node is factory_call or self._is_validation_call(node):
|
|
933
|
+
continue
|
|
934
|
+
if _is_narrowing_call(node, narrowing_helpers, self.local_bound_names):
|
|
935
|
+
continue
|
|
936
|
+
if contains_payload(node):
|
|
937
|
+
return False
|
|
938
|
+
return True
|
|
839
939
|
|
|
840
940
|
def _wrapper_value(self, access: ast.Attribute) -> ast.expr | None:
|
|
841
941
|
receiver = self._resolved_binding(access.value) if isinstance(access.value, ast.Name) else access.value
|
|
@@ -858,24 +958,15 @@ class _OriginResolver:
|
|
|
858
958
|
return next((keyword.value for keyword in receiver.keywords if keyword.arg == parameter), None)
|
|
859
959
|
|
|
860
960
|
def _wrapper_is_stable(self, constructor: ast.Call, summary: _WrapperSummary) -> bool:
|
|
861
|
-
|
|
862
|
-
return isinstance(value, ast.Name) and self._resolved_binding(value) is constructor
|
|
863
|
-
|
|
961
|
+
references = _WrapperReferences(constructor, summary, self._resolved_binding)
|
|
864
962
|
for node in self.scope:
|
|
865
|
-
if
|
|
866
|
-
isinstance(child, ast.Name) and refers_to_wrapper(child) for child in ast.walk(node)
|
|
867
|
-
):
|
|
963
|
+
if _reference_escapes(node, references.contains):
|
|
868
964
|
return False
|
|
869
|
-
if (
|
|
870
|
-
isinstance(node, ast.Attribute)
|
|
871
|
-
and refers_to_wrapper(node.value)
|
|
872
|
-
and (isinstance(node.ctx, ast.Store | ast.Del) or node.attr not in summary.accessors | summary.fields)
|
|
873
|
-
):
|
|
965
|
+
if isinstance(node, ast.Attribute) and references.unknown_member(node):
|
|
874
966
|
return False
|
|
875
|
-
if
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
refers_to_wrapper(keyword.value) for keyword in node.keywords
|
|
967
|
+
if isinstance(node, ast.Call) and any(
|
|
968
|
+
references.contains(argument)
|
|
969
|
+
for argument in (*node.args, *(keyword.value for keyword in node.keywords))
|
|
879
970
|
):
|
|
880
971
|
return False
|
|
881
972
|
return True
|
|
@@ -975,8 +1066,57 @@ class _OriginResolver:
|
|
|
975
1066
|
return False
|
|
976
1067
|
|
|
977
1068
|
|
|
1069
|
+
@dataclass(frozen=True, slots=True)
|
|
1070
|
+
class _WrapperReferences:
|
|
1071
|
+
constructor: ast.Call
|
|
1072
|
+
summary: _WrapperSummary
|
|
1073
|
+
resolve: Callable[[ast.Name], ast.expr]
|
|
1074
|
+
|
|
1075
|
+
def wrapper(self, value: ast.AST) -> bool:
|
|
1076
|
+
return value is self.constructor or (isinstance(value, ast.Name) and self.resolve(value) is self.constructor)
|
|
1077
|
+
|
|
1078
|
+
def payload(self, value: ast.AST) -> bool:
|
|
1079
|
+
if isinstance(value, ast.Name):
|
|
1080
|
+
value = self.resolve(value)
|
|
1081
|
+
if isinstance(value, ast.Call) and not value.args and not value.keywords:
|
|
1082
|
+
return (
|
|
1083
|
+
isinstance(value.func, ast.Attribute)
|
|
1084
|
+
and value.func.attr in self.summary.accessors
|
|
1085
|
+
and self.wrapper(value.func.value)
|
|
1086
|
+
)
|
|
1087
|
+
return isinstance(value, ast.Attribute) and value.attr in self.summary.fields and self.wrapper(value.value)
|
|
1088
|
+
|
|
1089
|
+
def contains(self, node: ast.AST) -> bool:
|
|
1090
|
+
return any(self.wrapper(child) or self.payload(child) for child in ast.walk(node))
|
|
1091
|
+
|
|
1092
|
+
def unknown_member(self, node: ast.Attribute) -> bool:
|
|
1093
|
+
if self.wrapper(node.value):
|
|
1094
|
+
return node.attr not in self.summary.accessors | self.summary.fields
|
|
1095
|
+
return self.payload(node.value) and node.attr not in _RECORD_METHODS
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def _reference_escapes(node: ast.AST, contains_reference: Callable[[ast.AST], bool]) -> bool:
|
|
1099
|
+
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.Lambda):
|
|
1100
|
+
return contains_reference(node)
|
|
1101
|
+
if isinstance(node, ast.List | ast.Tuple | ast.Set | ast.Dict):
|
|
1102
|
+
return contains_reference(node)
|
|
1103
|
+
if isinstance(node, ast.Attribute | ast.Subscript) and isinstance(node.ctx, ast.Store | ast.Del):
|
|
1104
|
+
return contains_reference(node.value)
|
|
1105
|
+
return False
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def _is_narrowing_call(call: ast.Call, helpers: frozenset[str], local_names: frozenset[str]) -> bool:
|
|
1109
|
+
return (
|
|
1110
|
+
isinstance(call.func, ast.Name)
|
|
1111
|
+
and call.func.id in helpers
|
|
1112
|
+
and call.func.id not in local_names
|
|
1113
|
+
and len(call.args) == 1
|
|
1114
|
+
and not call.keywords
|
|
1115
|
+
)
|
|
1116
|
+
|
|
1117
|
+
|
|
978
1118
|
def _record_access(node: ast.AST) -> _RecordAccess | None:
|
|
979
|
-
if isinstance(node, ast.Subscript) and _literal_string(node.slice) is not None:
|
|
1119
|
+
if isinstance(node, ast.Subscript) and isinstance(node.ctx, ast.Load) and _literal_string(node.slice) is not None:
|
|
980
1120
|
return _RecordAccess(node, node.value)
|
|
981
1121
|
if isinstance(node, ast.Call) and (receiver := _fixed_record_call_receiver(node)) is not None:
|
|
982
1122
|
return _RecordAccess(node, receiver)
|
|
@@ -1734,56 +1874,206 @@ def _wrapper_descriptor(statement: ast.stmt) -> bool:
|
|
|
1734
1874
|
def _wrapper_factory_summaries(
|
|
1735
1875
|
tree: ast.Module, imports: ImportIndex, summaries: _ModuleSummaries
|
|
1736
1876
|
) -> dict[str, _WrapperFactorySummary]:
|
|
1877
|
+
if not summaries.wrappers:
|
|
1878
|
+
return {}
|
|
1737
1879
|
counts = _suite_binding_counts(tree.body)
|
|
1738
1880
|
narrowing_helpers = _narrowing_helpers(tree, imports, counts)
|
|
1739
1881
|
factories: dict[str, _WrapperFactorySummary] = {}
|
|
1740
1882
|
for function in tree.body:
|
|
1741
1883
|
if not isinstance(function, ast.FunctionDef) or counts[function.name] != 1:
|
|
1742
1884
|
continue
|
|
1743
|
-
|
|
1744
|
-
if
|
|
1745
|
-
|
|
1746
|
-
wrapper = summaries.wrappers.get(returned.func.id)
|
|
1747
|
-
if wrapper is None:
|
|
1748
|
-
continue
|
|
1749
|
-
arguments = _factory_constructor_arguments(returned, wrapper)
|
|
1750
|
-
if arguments is None:
|
|
1751
|
-
continue
|
|
1752
|
-
scope = _own_scope(function)
|
|
1753
|
-
resolver = _OriginResolver(
|
|
1754
|
-
imports,
|
|
1755
|
-
summaries,
|
|
1756
|
-
frozenset(),
|
|
1757
|
-
frozenset(),
|
|
1758
|
-
frozenset(),
|
|
1759
|
-
{},
|
|
1760
|
-
_function_local_bound_names(function, scope),
|
|
1761
|
-
scope,
|
|
1762
|
-
)
|
|
1763
|
-
origins = {
|
|
1764
|
-
accessor: resolver.factory_argument_origins(arguments[wrapper.fields[field]], narrowing_helpers)
|
|
1765
|
-
for accessor, field in wrapper.accessors.items()
|
|
1766
|
-
}
|
|
1767
|
-
if any(origins.values()):
|
|
1768
|
-
factories[function.name] = _WrapperFactorySummary(wrapper, origins)
|
|
1885
|
+
summary = _wrapper_factory_summary(function, imports, summaries, narrowing_helpers)
|
|
1886
|
+
if summary is not None:
|
|
1887
|
+
factories[function.name] = summary
|
|
1769
1888
|
return factories
|
|
1770
1889
|
|
|
1771
1890
|
|
|
1772
|
-
def
|
|
1891
|
+
def _wrapper_factory_summary(
|
|
1892
|
+
function: ast.FunctionDef,
|
|
1893
|
+
imports: ImportIndex,
|
|
1894
|
+
summaries: _ModuleSummaries,
|
|
1895
|
+
narrowing_helpers: frozenset[str],
|
|
1896
|
+
) -> _WrapperFactorySummary | None:
|
|
1897
|
+
body = _factory_body(function)
|
|
1898
|
+
if body is None:
|
|
1899
|
+
return None
|
|
1900
|
+
returned, bindings = body
|
|
1901
|
+
if not isinstance(returned.func, ast.Name):
|
|
1902
|
+
return None
|
|
1903
|
+
scope = _own_scope(function)
|
|
1904
|
+
local_names = _function_local_bound_names(function, scope)
|
|
1905
|
+
wrapper = summaries.wrappers.get(returned.func.id)
|
|
1906
|
+
if wrapper is None or returned.func.id in local_names:
|
|
1907
|
+
return None
|
|
1908
|
+
arguments = _factory_constructor_arguments(returned, wrapper)
|
|
1909
|
+
if arguments is None:
|
|
1910
|
+
return None
|
|
1911
|
+
resolver = _OriginResolver(
|
|
1912
|
+
imports,
|
|
1913
|
+
summaries,
|
|
1914
|
+
frozenset(),
|
|
1915
|
+
frozenset(),
|
|
1916
|
+
_factory_response_names(bindings, imports, local_names),
|
|
1917
|
+
bindings,
|
|
1918
|
+
local_names,
|
|
1919
|
+
scope,
|
|
1920
|
+
)
|
|
1921
|
+
if not _factory_expressions_are_safe(function, returned, resolver, narrowing_helpers):
|
|
1922
|
+
return None
|
|
1923
|
+
origins = {
|
|
1924
|
+
accessor: resolver.factory_argument_origins(arguments[wrapper.fields[stored]], narrowing_helpers)
|
|
1925
|
+
for accessor, stored in wrapper.accessors.items()
|
|
1926
|
+
}
|
|
1927
|
+
parameters = frozenset(
|
|
1928
|
+
argument.arg for argument in (*function.args.posonlyargs, *function.args.args, *function.args.kwonlyargs)
|
|
1929
|
+
)
|
|
1930
|
+
forwarded = {
|
|
1931
|
+
accessor: _factory_forwarded_parameters(
|
|
1932
|
+
arguments[wrapper.fields[stored]], bindings, parameters, narrowing_helpers
|
|
1933
|
+
)
|
|
1934
|
+
for accessor, stored in wrapper.accessors.items()
|
|
1935
|
+
}
|
|
1936
|
+
if not any(origins.values()) and not any(forwarded.values()):
|
|
1937
|
+
return None
|
|
1938
|
+
return _WrapperFactorySummary(wrapper, origins, forwarded, function.args, narrowing_helpers)
|
|
1939
|
+
|
|
1940
|
+
|
|
1941
|
+
def _factory_body(function: ast.FunctionDef) -> tuple[ast.Call, dict[str, ast.expr]] | None:
|
|
1773
1942
|
statements = function.body[1:] if ast.get_docstring(function, clean=False) is not None else function.body
|
|
1774
1943
|
if (
|
|
1775
1944
|
function.decorator_list
|
|
1776
|
-
or
|
|
1777
|
-
or any(
|
|
1778
|
-
or
|
|
1945
|
+
or any((function.args.defaults, function.args.vararg, function.args.kwarg, function.type_params))
|
|
1946
|
+
or any(default is not None for default in function.args.kw_defaults)
|
|
1947
|
+
or not statements
|
|
1948
|
+
or not isinstance(statements[-1], ast.Return)
|
|
1779
1949
|
):
|
|
1780
1950
|
return None
|
|
1781
|
-
|
|
1782
|
-
if
|
|
1951
|
+
bindings = _factory_bindings(function, statements[:-1])
|
|
1952
|
+
if bindings is None:
|
|
1953
|
+
return None
|
|
1954
|
+
returned = statements[-1].value
|
|
1955
|
+
while isinstance(returned, ast.Name) and returned.id in bindings:
|
|
1956
|
+
returned = bindings[returned.id]
|
|
1957
|
+
if not isinstance(returned, ast.Call):
|
|
1783
1958
|
return None
|
|
1784
|
-
|
|
1959
|
+
return returned, bindings
|
|
1960
|
+
|
|
1961
|
+
|
|
1962
|
+
def _factory_bindings(function: ast.FunctionDef, statements: list[ast.stmt]) -> dict[str, ast.expr] | None:
|
|
1963
|
+
parameters = {
|
|
1964
|
+
argument.arg for argument in (*function.args.posonlyargs, *function.args.args, *function.args.kwonlyargs)
|
|
1965
|
+
}
|
|
1966
|
+
bindings: dict[str, ast.expr] = {}
|
|
1967
|
+
all_locals = _assigned_names(_own_scope(function))
|
|
1968
|
+
for statement in statements:
|
|
1969
|
+
if (
|
|
1970
|
+
isinstance(statement, ast.Assign)
|
|
1971
|
+
and len(statement.targets) == 1
|
|
1972
|
+
and isinstance(statement.targets[0], ast.Name)
|
|
1973
|
+
):
|
|
1974
|
+
name, value = statement.targets[0].id, statement.value
|
|
1975
|
+
elif (
|
|
1976
|
+
isinstance(statement, ast.AnnAssign)
|
|
1977
|
+
and isinstance(statement.target, ast.Name)
|
|
1978
|
+
and statement.value is not None
|
|
1979
|
+
):
|
|
1980
|
+
name, value = statement.target.id, statement.value
|
|
1981
|
+
else:
|
|
1982
|
+
return None
|
|
1983
|
+
if not isinstance(value, ast.Name | ast.Constant | ast.Call | ast.Dict | ast.List | ast.Tuple | ast.Set):
|
|
1984
|
+
return None
|
|
1985
|
+
unavailable = all_locals - bindings.keys()
|
|
1986
|
+
if (
|
|
1987
|
+
name in bindings
|
|
1988
|
+
or name in parameters
|
|
1989
|
+
or any(isinstance(child, ast.Name) and child.id in unavailable for child in ast.walk(value))
|
|
1990
|
+
):
|
|
1991
|
+
return None
|
|
1992
|
+
bindings[name] = value
|
|
1993
|
+
return bindings
|
|
1994
|
+
|
|
1995
|
+
|
|
1996
|
+
def _factory_expressions_are_safe(
|
|
1997
|
+
function: ast.FunctionDef,
|
|
1998
|
+
constructor: ast.Call,
|
|
1999
|
+
resolver: _OriginResolver,
|
|
2000
|
+
narrowing_helpers: frozenset[str],
|
|
2001
|
+
) -> bool:
|
|
2002
|
+
for statement in function.body:
|
|
2003
|
+
for node in ast.walk(statement):
|
|
2004
|
+
if isinstance(
|
|
2005
|
+
node,
|
|
2006
|
+
ast.NamedExpr
|
|
2007
|
+
| ast.Yield
|
|
2008
|
+
| ast.YieldFrom
|
|
2009
|
+
| ast.Await
|
|
2010
|
+
| ast.Lambda
|
|
2011
|
+
| ast.comprehension
|
|
2012
|
+
| ast.BinOp
|
|
2013
|
+
| ast.BoolOp
|
|
2014
|
+
| ast.UnaryOp
|
|
2015
|
+
| ast.Compare
|
|
2016
|
+
| ast.IfExp,
|
|
2017
|
+
):
|
|
2018
|
+
return False
|
|
2019
|
+
if not isinstance(node, ast.Call) or node is constructor:
|
|
2020
|
+
continue
|
|
2021
|
+
if resolver.factory_call_is_known(node):
|
|
2022
|
+
continue
|
|
2023
|
+
if _factory_http_request(node, resolver.imports, resolver.local_bound_names):
|
|
2024
|
+
continue
|
|
2025
|
+
if _is_narrowing_call(node, narrowing_helpers, resolver.local_bound_names):
|
|
2026
|
+
continue
|
|
2027
|
+
return False
|
|
2028
|
+
return True
|
|
2029
|
+
|
|
2030
|
+
|
|
2031
|
+
def _factory_http_request(call: ast.Call, imports: ImportIndex, local_names: frozenset[str]) -> bool:
|
|
2032
|
+
return not any(isinstance(node, ast.Name) and node.id in local_names for node in ast.walk(call.func)) and any(
|
|
2033
|
+
imports.resolves(call.func, sources=_HTTP_MODULES, symbol=method) for method in _HTTP_METHODS
|
|
2034
|
+
)
|
|
2035
|
+
|
|
2036
|
+
|
|
2037
|
+
def _factory_response_names(
|
|
2038
|
+
bindings: dict[str, ast.expr], imports: ImportIndex, local_names: frozenset[str]
|
|
2039
|
+
) -> frozenset[str]:
|
|
2040
|
+
responses: set[str] = set()
|
|
2041
|
+
for name, value in bindings.items():
|
|
2042
|
+
if (isinstance(value, ast.Call) and _factory_http_request(value, imports, local_names)) or (
|
|
2043
|
+
isinstance(value, ast.Name) and value.id in responses
|
|
2044
|
+
):
|
|
2045
|
+
responses.add(name)
|
|
2046
|
+
return frozenset(responses)
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
def _factory_forwarded_parameters(
|
|
2050
|
+
value: ast.expr,
|
|
2051
|
+
bindings: dict[str, ast.expr],
|
|
2052
|
+
parameters: frozenset[str],
|
|
2053
|
+
narrowing_helpers: frozenset[str],
|
|
2054
|
+
) -> frozenset[str]:
|
|
2055
|
+
if isinstance(value, ast.Name):
|
|
2056
|
+
if value.id in parameters:
|
|
2057
|
+
return frozenset({value.id})
|
|
2058
|
+
binding = bindings.get(value.id)
|
|
2059
|
+
if binding is not None:
|
|
2060
|
+
return _factory_forwarded_parameters(binding, bindings, parameters, narrowing_helpers)
|
|
2061
|
+
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id in narrowing_helpers:
|
|
2062
|
+
return _factory_forwarded_parameters(value.args[0], bindings, parameters, narrowing_helpers)
|
|
2063
|
+
return frozenset()
|
|
2064
|
+
|
|
2065
|
+
|
|
2066
|
+
def _factory_call_arguments(call: ast.Call, signature: ast.arguments) -> dict[str, ast.expr] | None:
|
|
2067
|
+
positional = tuple(argument.arg for argument in (*signature.posonlyargs, *signature.args))
|
|
2068
|
+
keyword_names = {argument.arg for argument in (*signature.args, *signature.kwonlyargs)}
|
|
2069
|
+
if len(call.args) > len(positional) or any(isinstance(argument, ast.Starred) for argument in call.args):
|
|
1785
2070
|
return None
|
|
1786
|
-
|
|
2071
|
+
arguments = dict(zip(positional, call.args, strict=False))
|
|
2072
|
+
for keyword in call.keywords:
|
|
2073
|
+
if keyword.arg not in keyword_names or keyword.arg in arguments:
|
|
2074
|
+
return None
|
|
2075
|
+
arguments[keyword.arg] = keyword.value
|
|
2076
|
+
return arguments if len(arguments) == len(positional) + len(signature.kwonlyargs) else None
|
|
1787
2077
|
|
|
1788
2078
|
|
|
1789
2079
|
def _factory_constructor_arguments(call: ast.Call, wrapper: _WrapperSummary) -> dict[str, ast.expr] | None:
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_resource_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
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.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/excessive_commentary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_any_mapping_types.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_delete_statement.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_dunder_all.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_fastapi_on_event.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.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_offset_pagination.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
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_restated_comment.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/no_select_star.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
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/phase_label_comment.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_class_row.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_library_fake.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
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/prefer_str_enum.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.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/redundant_docstring.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/typed_error_reasons.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.7.0 → sarj_python_lint-1.7.1}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|
|
File without changes
|