sarj-python-lint 1.48.0__tar.gz → 1.50.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/.gitignore +12 -1
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_registry.py +4 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_resource_provenance.py +126 -0
- sarj_python_lint-1.50.0/src/sarj_python_lint/rules/_runtime_bindings.py +113 -0
- sarj_python_lint-1.50.0/src/sarj_python_lint/rules/no_discarded_validation_result.py +732 -0
- sarj_python_lint-1.50.0/src/sarj_python_lint/rules/no_mutating_container_transform.py +472 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/LICENSE +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/README.md +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
|
@@ -32,7 +32,18 @@ Thumbs.db
|
|
|
32
32
|
.zed/
|
|
33
33
|
*.swp
|
|
34
34
|
|
|
35
|
-
.claude
|
|
35
|
+
.claude/*
|
|
36
|
+
!.claude/skills/
|
|
37
|
+
.claude/skills/*
|
|
38
|
+
!.claude/skills/audit-protocol/
|
|
39
|
+
.claude/skills/audit-protocol/*
|
|
40
|
+
!.claude/skills/audit-protocol/SKILL.md
|
|
41
|
+
!.claude/skills/lint-rule-generator/
|
|
42
|
+
.claude/skills/lint-rule-generator/*
|
|
43
|
+
!.claude/skills/lint-rule-generator/SKILL.md
|
|
44
|
+
!.claude/skills/test-value/
|
|
45
|
+
.claude/skills/test-value/*
|
|
46
|
+
!.claude/skills/test-value/SKILL.md
|
|
36
47
|
apps/docs/.wrangler/
|
|
37
48
|
apps/docs/.dev.vars*
|
|
38
49
|
!apps/docs/.dev.vars.example
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.50.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -43,6 +43,7 @@ from sarj_python_lint.rules.no_cors_wildcard_with_credentials import (
|
|
|
43
43
|
)
|
|
44
44
|
from sarj_python_lint.rules.no_database_programmability import NoDatabaseProgrammability
|
|
45
45
|
from sarj_python_lint.rules.no_delete_statement import NoDeleteStatement
|
|
46
|
+
from sarj_python_lint.rules.no_discarded_validation_result import NoDiscardedValidationResult
|
|
46
47
|
from sarj_python_lint.rules.no_dunder_all import NoDunderAll
|
|
47
48
|
from sarj_python_lint.rules.no_duplicate_dunder_all_entry import NoDuplicateDunderAllEntry
|
|
48
49
|
from sarj_python_lint.rules.no_excessive_cognitive_complexity import NoExcessiveCognitiveComplexity
|
|
@@ -69,6 +70,7 @@ from sarj_python_lint.rules.no_invalid_argument_name_suppression import NoInvali
|
|
|
69
70
|
from sarj_python_lint.rules.no_loguru_exc_info import NoLoguruExcInfo
|
|
70
71
|
from sarj_python_lint.rules.no_loguru_printf_format import NoLoguruPrintfFormat
|
|
71
72
|
from sarj_python_lint.rules.no_mocked_pydantic_value_object import NoMockedPydanticValueObject
|
|
73
|
+
from sarj_python_lint.rules.no_mutating_container_transform import NoMutatingContainerTransform
|
|
72
74
|
from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
|
|
73
75
|
from sarj_python_lint.rules.no_nullable_dependency_fallback import NoNullableDependencyFallback
|
|
74
76
|
from sarj_python_lint.rules.no_offset_pagination import NoOffsetPagination
|
|
@@ -249,6 +251,8 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
249
251
|
NoFixedSleepInLoop.id: NoFixedSleepInLoop,
|
|
250
252
|
NoInterpreterSourceArguments.id: NoInterpreterSourceArguments,
|
|
251
253
|
NoUnusedCopyResult.id: NoUnusedCopyResult,
|
|
254
|
+
NoDiscardedValidationResult.id: NoDiscardedValidationResult,
|
|
255
|
+
NoMutatingContainerTransform.id: NoMutatingContainerTransform,
|
|
252
256
|
NoBeforeValidatorInputMutation.id: NoBeforeValidatorInputMutation,
|
|
253
257
|
NoInputModelMutation.id: NoInputModelMutation,
|
|
254
258
|
NoDatabaseProgrammability.id: NoDatabaseProgrammability,
|
|
@@ -8,9 +8,12 @@ from typing import TYPE_CHECKING, final
|
|
|
8
8
|
|
|
9
9
|
from sarj_python_lint.rules._ast_index import has_own_yield
|
|
10
10
|
from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
|
|
11
|
+
from sarj_python_lint.rules._runtime_bindings import RuntimeBindings
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
14
17
|
from sarj_python_lint._file_context import PythonFileContext
|
|
15
18
|
|
|
16
19
|
|
|
@@ -76,6 +79,7 @@ class ResourceProvenance:
|
|
|
76
79
|
self.escape_cache: dict[tuple[ast.AST, bool], bool] = {}
|
|
77
80
|
self.async_contract_cache: dict[ast.AsyncFunctionDef, bool] = {}
|
|
78
81
|
self.import_contract_cache: dict[str, bool] = {}
|
|
82
|
+
self.runtime_import_cache: dict[ast.alias, bool] = {}
|
|
79
83
|
self.wildcard = False
|
|
80
84
|
self.mutated: set[str] = set()
|
|
81
85
|
self.external_bindings: set[str] = set()
|
|
@@ -133,6 +137,113 @@ class ResourceProvenance:
|
|
|
133
137
|
scope = self.scope(scope)
|
|
134
138
|
return None
|
|
135
139
|
|
|
140
|
+
@cached_property
|
|
141
|
+
def runtime_bindings(self) -> RuntimeBindings:
|
|
142
|
+
return RuntimeBindings(self.context, self.bindings)
|
|
143
|
+
|
|
144
|
+
def runtime_binding(self, name: str, at: ast.AST) -> ast.AST | None:
|
|
145
|
+
nodes = self.runtime_bindings.lookup(self.scope(at), name)
|
|
146
|
+
if not nodes:
|
|
147
|
+
return None
|
|
148
|
+
return nodes[0] if len(nodes) == 1 or self._same_import_bindings(list(nodes)) else None
|
|
149
|
+
|
|
150
|
+
def runtime_import_name(self, node: ast.expr) -> str | None:
|
|
151
|
+
root = _root_name(node)
|
|
152
|
+
if self.wildcard or self.dynamic_namespace or root is None or root in self.mutated:
|
|
153
|
+
return None
|
|
154
|
+
binding = self.runtime_binding(root, node)
|
|
155
|
+
if not isinstance(binding, ast.alias):
|
|
156
|
+
return None
|
|
157
|
+
qualified = import_name_for_binding(node, binding, self.context.parents.get(binding))
|
|
158
|
+
if qualified is None or not self._owner_namespace_untouched(qualified.partition(".")[0]):
|
|
159
|
+
return None
|
|
160
|
+
if binding not in self.runtime_import_cache:
|
|
161
|
+
self.runtime_import_cache[binding] = all(
|
|
162
|
+
self.runtime_binding(root, read) is not binding or self._safe_import_use(read)
|
|
163
|
+
for read in self.reads.get(root, ())
|
|
164
|
+
)
|
|
165
|
+
return qualified if self.runtime_import_cache[binding] else None
|
|
166
|
+
|
|
167
|
+
@cached_property
|
|
168
|
+
def mock_namespace_patching(self) -> bool:
|
|
169
|
+
return any(
|
|
170
|
+
self.runtime_import_name(node.func)
|
|
171
|
+
in {
|
|
172
|
+
"unittest.mock.patch",
|
|
173
|
+
"unittest.mock.patch.object",
|
|
174
|
+
"unittest.mock.patch.multiple",
|
|
175
|
+
"unittest.mock.patch.dict",
|
|
176
|
+
}
|
|
177
|
+
for node in self.context.nodes(ast.Call)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
@cached_property
|
|
181
|
+
def imported_namespace_hazards(self) -> frozenset[Path]:
|
|
182
|
+
hazards: set[Path] = set()
|
|
183
|
+
for node in self.context.nodes(ast.Name, ast.Attribute):
|
|
184
|
+
match node:
|
|
185
|
+
case ast.Attribute(value=value, ctx=ast.Store() | ast.Del()):
|
|
186
|
+
hazards.update(self._namespace_paths(value, include_root=True))
|
|
187
|
+
case ast.Name(ctx=ast.Load()) | ast.Attribute(ctx=ast.Load()) if not self._safe_import_use(node):
|
|
188
|
+
hazards.update(self._namespace_paths(node, include_root=False))
|
|
189
|
+
case _:
|
|
190
|
+
pass
|
|
191
|
+
return frozenset(hazards)
|
|
192
|
+
|
|
193
|
+
@cached_property
|
|
194
|
+
def _namespace_aliases(self) -> dict[str, tuple[ast.alias, ...]]:
|
|
195
|
+
aliases: dict[str, list[ast.alias]] = defaultdict(list)
|
|
196
|
+
for binding in self.context.nodes(ast.alias):
|
|
197
|
+
parent = self.context.parents.get(binding)
|
|
198
|
+
name = binding.asname or (
|
|
199
|
+
binding.name.partition(".")[0] if isinstance(parent, ast.Import) else binding.name
|
|
200
|
+
)
|
|
201
|
+
aliases[name].append(binding)
|
|
202
|
+
return {name: tuple(bindings) for name, bindings in aliases.items()}
|
|
203
|
+
|
|
204
|
+
def _namespace_paths(self, expression: ast.expr, *, include_root: bool) -> set[Path]:
|
|
205
|
+
attributes: list[str] = []
|
|
206
|
+
root = expression
|
|
207
|
+
while isinstance(root, ast.Attribute):
|
|
208
|
+
attributes.append(root.attr)
|
|
209
|
+
root = root.value
|
|
210
|
+
if not isinstance(root, ast.Name):
|
|
211
|
+
return set()
|
|
212
|
+
binding = self.runtime_binding(root.id, expression)
|
|
213
|
+
aliases: tuple[ast.alias, ...]
|
|
214
|
+
match binding:
|
|
215
|
+
case ast.alias():
|
|
216
|
+
aliases = (binding,)
|
|
217
|
+
case None:
|
|
218
|
+
aliases = self._namespace_aliases.get(root.id, ())
|
|
219
|
+
case _:
|
|
220
|
+
return set()
|
|
221
|
+
paths: set[Path] = set()
|
|
222
|
+
for alias in aliases:
|
|
223
|
+
path = self._namespace_module_path(alias, tuple(reversed(attributes)))
|
|
224
|
+
if path is not None:
|
|
225
|
+
paths.add(path)
|
|
226
|
+
if include_root and attributes:
|
|
227
|
+
base = self._namespace_module_path(alias, ())
|
|
228
|
+
if base is not None:
|
|
229
|
+
paths.add(base)
|
|
230
|
+
return paths
|
|
231
|
+
|
|
232
|
+
def _namespace_module_path(self, binding: ast.alias, attributes: tuple[str, ...]) -> Path | None:
|
|
233
|
+
parent = self.context.parents.get(binding)
|
|
234
|
+
match parent:
|
|
235
|
+
case ast.Import():
|
|
236
|
+
prefix = binding.name if binding.asname else binding.name.partition(".")[0]
|
|
237
|
+
level = 0
|
|
238
|
+
case ast.ImportFrom(module=module, level=level):
|
|
239
|
+
prefix = ".".join(part for part in (module, binding.name) if part)
|
|
240
|
+
case _:
|
|
241
|
+
return None
|
|
242
|
+
module = ".".join((prefix, *attributes))
|
|
243
|
+
return self.context.session.local_source.resolve_module(
|
|
244
|
+
self.context.path, module, level, facts=self.context.session.first_party
|
|
245
|
+
)
|
|
246
|
+
|
|
136
247
|
def _same_import_bindings(self, nodes: list[ast.AST]) -> bool:
|
|
137
248
|
targets: set[tuple[str, str | None, int, str]] = set()
|
|
138
249
|
for node in nodes:
|
|
@@ -417,6 +528,21 @@ def _root_name(node: ast.expr) -> str | None:
|
|
|
417
528
|
return node.id if isinstance(node, ast.Name) else None
|
|
418
529
|
|
|
419
530
|
|
|
531
|
+
def import_name_for_binding(expression: ast.expr, binding: ast.alias, parent: ast.AST | None) -> str | None:
|
|
532
|
+
attributes: list[str] = []
|
|
533
|
+
while isinstance(expression, ast.Attribute):
|
|
534
|
+
attributes.append(expression.attr)
|
|
535
|
+
expression = expression.value
|
|
536
|
+
match parent:
|
|
537
|
+
case ast.Import():
|
|
538
|
+
prefix = binding.name if binding.asname else binding.name.partition(".")[0]
|
|
539
|
+
case ast.ImportFrom(module=str(module), level=0):
|
|
540
|
+
prefix = f"{module}.{binding.name}"
|
|
541
|
+
case _:
|
|
542
|
+
return None
|
|
543
|
+
return ".".join((prefix, *reversed(attributes)))
|
|
544
|
+
|
|
545
|
+
|
|
420
546
|
def _bound_names(node: ast.AST) -> tuple[str, ...]:
|
|
421
547
|
match node:
|
|
422
548
|
case ast.Name(id=name, ctx=ast.Store() | ast.Del()) | ast.arg(arg=name):
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
import symtable
|
|
6
|
+
from typing import TYPE_CHECKING, final
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Mapping, Sequence
|
|
11
|
+
|
|
12
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@final
|
|
16
|
+
class RuntimeBindings:
|
|
17
|
+
def __init__(
|
|
18
|
+
self, context: PythonFileContext, bindings: Mapping[ast.AST | None, Mapping[str, Sequence[ast.AST]]]
|
|
19
|
+
) -> None:
|
|
20
|
+
self.bindings = bindings
|
|
21
|
+
self.tables: dict[int, symtable.SymbolTable] = {}
|
|
22
|
+
self.parents: dict[int, int | None] = {}
|
|
23
|
+
self.by_scope: dict[ast.AST, int] = {}
|
|
24
|
+
self.scopes: dict[int, ast.AST] = {}
|
|
25
|
+
self.mutable: set[tuple[int, str]] = set()
|
|
26
|
+
self.module: int | None = None
|
|
27
|
+
tree = context.tree
|
|
28
|
+
if tree is None:
|
|
29
|
+
return
|
|
30
|
+
try:
|
|
31
|
+
root = symtable.symtable(context.source, str(context.path), "exec")
|
|
32
|
+
except SyntaxError, UnicodeError, RecursionError:
|
|
33
|
+
return
|
|
34
|
+
self.module = root.get_id()
|
|
35
|
+
self._collect(root)
|
|
36
|
+
self._map_scopes(context, tree, root)
|
|
37
|
+
self._index_writes()
|
|
38
|
+
|
|
39
|
+
def lookup(self, scope: ast.AST | None, name: str) -> Sequence[ast.AST] | None:
|
|
40
|
+
if scope is None or (table_id := self.by_scope.get(scope)) is None:
|
|
41
|
+
return None
|
|
42
|
+
symbol = _symbol(self.tables[table_id], name)
|
|
43
|
+
if symbol is None or (owner := self._owner(table_id, symbol)) is None or (owner, name) in self.mutable:
|
|
44
|
+
return None
|
|
45
|
+
owner_scope = self.scopes.get(owner)
|
|
46
|
+
return self.bindings.get(owner_scope, {}).get(name) if owner_scope is not None else None
|
|
47
|
+
|
|
48
|
+
def _collect(self, root: symtable.SymbolTable) -> None:
|
|
49
|
+
pending: list[tuple[symtable.SymbolTable, int | None]] = [(root, None)]
|
|
50
|
+
while pending:
|
|
51
|
+
table, parent = pending.pop()
|
|
52
|
+
identity = table.get_id()
|
|
53
|
+
self.tables[identity] = table
|
|
54
|
+
self.parents[identity] = parent
|
|
55
|
+
pending.extend((child, identity) for child in table.get_children())
|
|
56
|
+
|
|
57
|
+
def _map_scopes(self, context: PythonFileContext, tree: ast.Module, root: symtable.SymbolTable) -> None:
|
|
58
|
+
candidates: dict[tuple[symtable.SymbolTableType, str, int], list[int]] = defaultdict(list)
|
|
59
|
+
for identity, table in self.tables.items():
|
|
60
|
+
candidates[table.get_type(), table.get_name(), table.get_lineno()].append(identity)
|
|
61
|
+
self.by_scope[tree] = root.get_id()
|
|
62
|
+
self.scopes[root.get_id()] = tree
|
|
63
|
+
for node in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef):
|
|
64
|
+
kind = (
|
|
65
|
+
symtable.SymbolTableType.CLASS if isinstance(node, ast.ClassDef) else symtable.SymbolTableType.FUNCTION
|
|
66
|
+
)
|
|
67
|
+
matches = candidates.get((kind, node.name, node.lineno), ())
|
|
68
|
+
if len(matches) == 1:
|
|
69
|
+
[identity] = matches
|
|
70
|
+
self.by_scope[node] = identity
|
|
71
|
+
self.scopes[identity] = node
|
|
72
|
+
|
|
73
|
+
def _index_writes(self) -> None:
|
|
74
|
+
for identity, table in self.tables.items():
|
|
75
|
+
if identity == self.module:
|
|
76
|
+
continue
|
|
77
|
+
for symbol in table.get_symbols():
|
|
78
|
+
if not (symbol.is_global() or symbol.is_nonlocal()):
|
|
79
|
+
continue
|
|
80
|
+
if not (symbol.is_assigned() or symbol.is_imported() or symbol.is_namespace()):
|
|
81
|
+
continue
|
|
82
|
+
owner = self._owner(identity, symbol)
|
|
83
|
+
if owner is not None:
|
|
84
|
+
self.mutable.add((owner, symbol.get_name()))
|
|
85
|
+
|
|
86
|
+
def _owner(self, identity: int, symbol: symtable.Symbol) -> int | None:
|
|
87
|
+
if symbol.is_global():
|
|
88
|
+
return self.module
|
|
89
|
+
if symbol.is_local():
|
|
90
|
+
return identity
|
|
91
|
+
if not symbol.is_free():
|
|
92
|
+
return None
|
|
93
|
+
parent = self.parents.get(identity)
|
|
94
|
+
while parent is not None:
|
|
95
|
+
table = self.tables[parent]
|
|
96
|
+
if table.get_type() is symtable.SymbolTableType.MODULE:
|
|
97
|
+
break
|
|
98
|
+
if table.get_type() is symtable.SymbolTableType.CLASS and symbol.get_name() == "__class__":
|
|
99
|
+
# The compiler binds this cell to the lexically defined class.
|
|
100
|
+
return None
|
|
101
|
+
if table.get_type() is not symtable.SymbolTableType.CLASS:
|
|
102
|
+
ancestor = _symbol(table, symbol.get_name())
|
|
103
|
+
if ancestor is not None and ancestor.is_local():
|
|
104
|
+
return parent
|
|
105
|
+
parent = self.parents.get(parent)
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _symbol(table: symtable.SymbolTable, name: str) -> symtable.Symbol | None:
|
|
110
|
+
try:
|
|
111
|
+
return table.lookup(name)
|
|
112
|
+
except KeyError:
|
|
113
|
+
return None
|