sarj-python-lint 1.54.0__tar.gz → 1.56.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.54.0 → sarj_python_lint-1.56.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +434 -26
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +1 -1
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/.gitignore +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/LICENSE +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/README.md +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/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.
|
|
3
|
+
Version: 1.56.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/
|
|
@@ -73,6 +73,7 @@ class _DataUnions:
|
|
|
73
73
|
def __init__(self, context: PythonFileContext, tree: ast.Module) -> None:
|
|
74
74
|
self.context = context
|
|
75
75
|
self.provenance = ResourceProvenance(context, require_static_namespace=True)
|
|
76
|
+
self.provenance.mutated = _scoped_module_mutations(self.provenance, tree)
|
|
76
77
|
bindings = self.provenance.bindings.get(tree, {})
|
|
77
78
|
self.classes = {
|
|
78
79
|
node.name: node
|
|
@@ -80,13 +81,29 @@ class _DataUnions:
|
|
|
80
81
|
if isinstance(node, ast.ClassDef)
|
|
81
82
|
and len(bindings.get(node.name, ())) == 1
|
|
82
83
|
and node.name not in self.provenance.mutated
|
|
84
|
+
and node.name not in self.provenance.external_bindings
|
|
83
85
|
}
|
|
84
86
|
self.aliases: dict[str, ast.expr] = {}
|
|
85
87
|
self.declarations: dict[str, ast.stmt] = {}
|
|
88
|
+
self.values: dict[str, ast.expr] = {}
|
|
89
|
+
self.metadata_aliases = _metadata_alias_graph(tree, bindings)
|
|
90
|
+
self.metadata_mutation_cache: dict[str, bool | None] = {}
|
|
91
|
+
self.metadata_writes = _metadata_attribute_writes(context, tree, self.provenance)
|
|
86
92
|
for node in tree.body:
|
|
87
|
-
|
|
93
|
+
binding = _value_binding(node)
|
|
94
|
+
if binding is not None:
|
|
95
|
+
bound_name, bound_value = binding
|
|
96
|
+
if len(bindings.get(bound_name, ())) == 1:
|
|
97
|
+
self.values[bound_name] = bound_value
|
|
98
|
+
value = _alias_value(node, context, self.provenance)
|
|
88
99
|
name = _alias_name(node)
|
|
89
|
-
if
|
|
100
|
+
if (
|
|
101
|
+
value is not None
|
|
102
|
+
and name is not None
|
|
103
|
+
and len(bindings.get(name, ())) == 1
|
|
104
|
+
and name not in self.provenance.external_bindings
|
|
105
|
+
and name not in self.provenance.mutated
|
|
106
|
+
):
|
|
90
107
|
self.aliases[name] = value
|
|
91
108
|
self.declarations[name] = node
|
|
92
109
|
self.records: dict[str, _Record | None] = {}
|
|
@@ -161,25 +178,199 @@ class _DataUnions:
|
|
|
161
178
|
)
|
|
162
179
|
|
|
163
180
|
def _discriminator_metadata(self, node: ast.expr) -> bool:
|
|
164
|
-
|
|
181
|
+
seen: frozenset[str] = frozenset()
|
|
182
|
+
for _ in range(_MAX_EXPANSION):
|
|
183
|
+
reference = self._metadata_reference(node, seen)
|
|
184
|
+
if isinstance(reference, bool):
|
|
185
|
+
return reference
|
|
186
|
+
if reference is not None:
|
|
187
|
+
node, seen = reference.node, reference.aliases
|
|
188
|
+
continue
|
|
189
|
+
if not isinstance(node, ast.Call):
|
|
190
|
+
return self._metadata_aliases_discriminate(seen, callable_metadata=False)
|
|
191
|
+
step = self._metadata_step(node)
|
|
192
|
+
if isinstance(step, bool):
|
|
193
|
+
return self._metadata_aliases_discriminate(seen, callable_metadata=step)
|
|
194
|
+
node = step
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
def _metadata_reference(self, node: ast.expr, seen: frozenset[str]) -> _Expansion | bool | None:
|
|
198
|
+
if isinstance(node, ast.Name) and node.id in self.values:
|
|
199
|
+
return True if node.id in seen else _Expansion(self.values[node.id], seen | {node.id})
|
|
200
|
+
projected = self._metadata_projection(node)
|
|
201
|
+
if projected is None or isinstance(projected, bool):
|
|
202
|
+
return projected
|
|
203
|
+
if not projected.aliases.isdisjoint(seen) or any(
|
|
204
|
+
self._metadata_projection_escaped(name) for name in projected.aliases
|
|
205
|
+
):
|
|
206
|
+
return True
|
|
207
|
+
return _Expansion(projected.node, seen | projected.aliases)
|
|
208
|
+
|
|
209
|
+
def _metadata_projection(self, node: ast.expr) -> _Expansion | bool | None:
|
|
210
|
+
if isinstance(node, ast.Subscript):
|
|
211
|
+
return self._metadata_tuple_item(node)
|
|
212
|
+
if not isinstance(node, ast.Attribute) or not isinstance(node.value, ast.Name):
|
|
213
|
+
return None
|
|
214
|
+
bindings = self.provenance.bindings.get(self.context.tree, {}).get(node.value.id, ())
|
|
215
|
+
if len(bindings) != 1 or not isinstance(bindings[0], ast.ClassDef):
|
|
216
|
+
return None
|
|
217
|
+
value = _namespace_metadata_value(bindings[0], node.attr)
|
|
218
|
+
return _Expansion(value, frozenset({node.value.id})) if value is not None else None
|
|
219
|
+
|
|
220
|
+
def _metadata_tuple_item(self, node: ast.Subscript) -> _Expansion | bool | None:
|
|
221
|
+
value = node.value
|
|
222
|
+
names: frozenset[str] = frozenset()
|
|
223
|
+
for _ in range(_MAX_EXPANSION):
|
|
224
|
+
if not isinstance(value, ast.Name) or value.id not in self.values:
|
|
225
|
+
break
|
|
226
|
+
if value.id in names:
|
|
227
|
+
return True
|
|
228
|
+
names |= {value.id}
|
|
229
|
+
value = self.values[value.id]
|
|
230
|
+
else:
|
|
231
|
+
return True
|
|
232
|
+
if not isinstance(value, ast.Tuple):
|
|
233
|
+
return None
|
|
234
|
+
if len(value.elts) > _MAX_EXPANSION:
|
|
235
|
+
return True
|
|
236
|
+
if any(isinstance(item, ast.Starred) for item in value.elts):
|
|
237
|
+
return None
|
|
238
|
+
index = _metadata_tuple_index(node.slice)
|
|
239
|
+
if index is None:
|
|
240
|
+
return True
|
|
241
|
+
if not -len(value.elts) <= index < len(value.elts):
|
|
242
|
+
return None
|
|
243
|
+
return _Expansion(value.elts[index], names)
|
|
244
|
+
|
|
245
|
+
def _metadata_projection_escaped(self, name: str) -> bool:
|
|
246
|
+
bindings = self.provenance.bindings.get(self.context.tree, {}).get(name, ())
|
|
247
|
+
reads = self.provenance.reads.get(name, ())
|
|
248
|
+
if len(bindings) != 1 or len(reads) > _MAX_EXPANSION:
|
|
249
|
+
return True
|
|
250
|
+
for read in reads:
|
|
251
|
+
if self.provenance.binding(name, read) is not bindings[0]:
|
|
252
|
+
continue
|
|
253
|
+
projection = self.context.parents.get(read)
|
|
254
|
+
if not isinstance(projection, ast.Attribute | ast.Subscript) or projection.value is not read:
|
|
255
|
+
return True
|
|
256
|
+
if not self._metadata_annotation_member(projection):
|
|
257
|
+
return True
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
def _metadata_annotation_member(self, node: ast.expr) -> bool:
|
|
261
|
+
parent = self.context.parents.get(node)
|
|
262
|
+
if not isinstance(parent, ast.Tuple) or len(parent.elts) > _MAX_EXPANSION:
|
|
165
263
|
return False
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
264
|
+
owner = self.context.parents.get(parent)
|
|
265
|
+
return (
|
|
266
|
+
isinstance(owner, ast.Subscript)
|
|
267
|
+
and owner.slice is parent
|
|
268
|
+
and self._typing_symbol(owner.value) == "Annotated"
|
|
269
|
+
and node in parent.elts[1:]
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def _metadata_step(self, node: ast.Call) -> ast.expr | bool:
|
|
273
|
+
if not _untouched(node.func, self.provenance):
|
|
274
|
+
return False
|
|
275
|
+
discriminator = next((keyword.value for keyword in node.keywords if keyword.arg == "discriminator"), None)
|
|
276
|
+
if self._metadata_constructor(node.func, _DISCRIMINATOR_SOURCES, "Discriminator"):
|
|
277
|
+
argument = node.args[0] if node.args else discriminator
|
|
278
|
+
return argument is not None and not self._string_discriminator(argument)
|
|
279
|
+
if self._metadata_constructor(node.func, _FIELD_SOURCES, "Field") and discriminator is not None:
|
|
280
|
+
return discriminator
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
def _metadata_aliases_discriminate(self, names: frozenset[str], *, callable_metadata: bool) -> bool:
|
|
284
|
+
mutations = [self._metadata_mutated(name) for name in names]
|
|
285
|
+
return any(mutation is None for mutation in mutations) or (callable_metadata and not any(mutations))
|
|
286
|
+
|
|
287
|
+
def _metadata_mutated(self, name: str) -> bool | None:
|
|
288
|
+
if name in self.metadata_mutation_cache:
|
|
289
|
+
return self.metadata_mutation_cache[name]
|
|
290
|
+
pending = [name]
|
|
291
|
+
seen: set[str] = set()
|
|
292
|
+
mutated = False
|
|
293
|
+
for _ in range(_MAX_EXPANSION):
|
|
294
|
+
if not pending:
|
|
295
|
+
self.metadata_mutation_cache.update(dict.fromkeys(seen, mutated))
|
|
296
|
+
return mutated
|
|
297
|
+
current = pending.pop()
|
|
298
|
+
if current in seen:
|
|
299
|
+
continue
|
|
300
|
+
seen.add(current)
|
|
301
|
+
if current in self.provenance.external_bindings or self._metadata_escaped(current):
|
|
302
|
+
return None
|
|
303
|
+
if current in self.provenance.mutated:
|
|
304
|
+
if not self._metadata_string_writes(current):
|
|
305
|
+
return None
|
|
306
|
+
mutated = True
|
|
307
|
+
pending.extend(self.metadata_aliases.get(current, set()).difference(seen))
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
def _metadata_string_writes(self, name: str) -> bool:
|
|
311
|
+
writes = self.metadata_writes.get(name, ())
|
|
312
|
+
return (
|
|
313
|
+
bool(writes)
|
|
314
|
+
and len(writes) <= _MAX_EXPANSION
|
|
315
|
+
and all(attribute == "discriminator" and self._string_discriminator(value) for attribute, value in writes)
|
|
177
316
|
)
|
|
178
317
|
|
|
318
|
+
def _metadata_escaped(self, name: str) -> bool:
|
|
319
|
+
reads = self.provenance.reads.get(name, ())
|
|
320
|
+
if len(reads) > _MAX_EXPANSION:
|
|
321
|
+
return True
|
|
322
|
+
bindings = self.provenance.bindings.get(self.context.tree, {}).get(name, ())
|
|
323
|
+
if len(bindings) != 1:
|
|
324
|
+
return True
|
|
325
|
+
for read in reads:
|
|
326
|
+
if self.provenance.binding(name, read) is not bindings[0]:
|
|
327
|
+
continue
|
|
328
|
+
node: ast.AST | None = read
|
|
329
|
+
for _ in range(_MAX_EXPANSION):
|
|
330
|
+
node = self.context.parents.get(node)
|
|
331
|
+
if isinstance(node, ast.Call):
|
|
332
|
+
return True
|
|
333
|
+
if node is None or isinstance(node, ast.stmt):
|
|
334
|
+
break
|
|
335
|
+
else:
|
|
336
|
+
return True
|
|
337
|
+
return False
|
|
338
|
+
|
|
339
|
+
def _metadata_constructor(self, node: ast.expr, sources: frozenset[str], symbol: str) -> bool:
|
|
340
|
+
imports = self.context.module_imports
|
|
341
|
+
return imports.resolves(node, sources=sources, symbol=symbol) or (
|
|
342
|
+
isinstance(node, ast.Attribute)
|
|
343
|
+
and node.attr == symbol
|
|
344
|
+
and imports.resolved_qualified_name(node.value) in sources
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def _string_discriminator(self, node: ast.expr) -> bool:
|
|
348
|
+
pending = [_Expansion(node)]
|
|
349
|
+
for _ in range(_MAX_EXPANSION):
|
|
350
|
+
if not pending:
|
|
351
|
+
return True
|
|
352
|
+
item = pending.pop()
|
|
353
|
+
node = item.node
|
|
354
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
355
|
+
continue
|
|
356
|
+
if (
|
|
357
|
+
isinstance(node, ast.Name)
|
|
358
|
+
and node.id in self.values
|
|
359
|
+
and node.id not in item.aliases
|
|
360
|
+
and node.id not in self.provenance.mutated
|
|
361
|
+
and node.id not in self.provenance.external_bindings
|
|
362
|
+
):
|
|
363
|
+
pending.append(_Expansion(self.values[node.id], item.aliases | {node.id}))
|
|
364
|
+
elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
365
|
+
pending.extend(_Expansion(child, item.aliases) for child in (node.left, node.right))
|
|
366
|
+
else:
|
|
367
|
+
return False
|
|
368
|
+
return False
|
|
369
|
+
|
|
179
370
|
def _typing_symbol(self, node: ast.expr) -> str | None:
|
|
180
371
|
if not _untouched(node, self.provenance):
|
|
181
372
|
return None
|
|
182
|
-
return self.context.
|
|
373
|
+
return self.context.module_imports.resolved_symbol(node, sources=TYPING_SOURCES)
|
|
183
374
|
|
|
184
375
|
def _variants(self, expression: ast.expr) -> set[str] | None:
|
|
185
376
|
leaves = self._leaves(expression)
|
|
@@ -251,6 +442,8 @@ class _DataUnions:
|
|
|
251
442
|
return uncertain
|
|
252
443
|
|
|
253
444
|
def _field_domain(self, field: ast.AnnAssign, *, required: bool) -> _Domain:
|
|
445
|
+
if _direct_initvar_field(field, self.context, self.provenance):
|
|
446
|
+
return _Domain()
|
|
254
447
|
node = field.annotation
|
|
255
448
|
aliases: set[str] = set()
|
|
256
449
|
for _ in range(_MAX_EXPANSION):
|
|
@@ -296,13 +489,17 @@ class _DataUnions:
|
|
|
296
489
|
return _Domain(frozenset({_tag(None)}))
|
|
297
490
|
if isinstance(node, ast.Name | ast.Attribute) and (
|
|
298
491
|
(isinstance(node, ast.Name) and node.id in self.classes)
|
|
299
|
-
or
|
|
492
|
+
or (
|
|
493
|
+
_untouched(node, self.provenance)
|
|
494
|
+
and self.context.module_imports.resolved_qualified_name(node) is not None
|
|
495
|
+
)
|
|
300
496
|
):
|
|
301
497
|
return _Domain(unresolved=True)
|
|
302
498
|
if (
|
|
303
499
|
isinstance(node, ast.Name)
|
|
304
500
|
and node.id in _NON_LITERAL_BUILTINS
|
|
305
|
-
and self.context.
|
|
501
|
+
and self.context.module_imports.builtin_is_unshadowed(node.id)
|
|
502
|
+
and _untouched(node, self.provenance)
|
|
306
503
|
):
|
|
307
504
|
return _Domain()
|
|
308
505
|
if not isinstance(node, ast.Subscript):
|
|
@@ -313,7 +510,8 @@ class _DataUnions:
|
|
|
313
510
|
if symbol is not None or (
|
|
314
511
|
isinstance(node.value, ast.Name)
|
|
315
512
|
and node.value.id in {"list", "dict", "tuple", "set", "frozenset", "type"}
|
|
316
|
-
and self.context.
|
|
513
|
+
and self.context.module_imports.builtin_is_unshadowed(node.value.id)
|
|
514
|
+
and _untouched(node.value, self.provenance)
|
|
317
515
|
):
|
|
318
516
|
return _Domain()
|
|
319
517
|
return _Domain(opaque=True)
|
|
@@ -363,6 +561,26 @@ def _wrapped_annotation(node: ast.Subscript, symbol: str | None, *, required: bo
|
|
|
363
561
|
return None
|
|
364
562
|
|
|
365
563
|
|
|
564
|
+
def _direct_initvar_field(field: ast.AnnAssign, context: PythonFileContext, provenance: ResourceProvenance) -> bool:
|
|
565
|
+
if field.value is not None or not isinstance(field.annotation, ast.Subscript):
|
|
566
|
+
return False
|
|
567
|
+
target = field.annotation.value
|
|
568
|
+
if not _untouched(target, provenance) or not context.module_imports.resolves(
|
|
569
|
+
target, sources=_DATACLASS_SOURCES, symbol="InitVar"
|
|
570
|
+
):
|
|
571
|
+
return False
|
|
572
|
+
owner = context.parents.get(field)
|
|
573
|
+
if not isinstance(owner, ast.ClassDef):
|
|
574
|
+
return False
|
|
575
|
+
for decorator in owner.decorator_list:
|
|
576
|
+
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
577
|
+
if _untouched(target, provenance) and context.module_imports.resolves(
|
|
578
|
+
target, sources=_DATACLASS_SOURCES, symbol="dataclass"
|
|
579
|
+
):
|
|
580
|
+
return True
|
|
581
|
+
return False
|
|
582
|
+
|
|
583
|
+
|
|
366
584
|
def _record(node: ast.ClassDef, context: PythonFileContext, provenance: ResourceProvenance) -> _Record | None:
|
|
367
585
|
required = _record_required(node, context, provenance)
|
|
368
586
|
if required is None:
|
|
@@ -381,9 +599,37 @@ def _record(node: ast.ClassDef, context: PythonFileContext, provenance: Resource
|
|
|
381
599
|
continue
|
|
382
600
|
else:
|
|
383
601
|
return None
|
|
602
|
+
field_names = frozenset(fields)
|
|
603
|
+
if any(_class_annotation_shadowed(field.annotation, field_names, context) for field in fields.values()):
|
|
604
|
+
return None
|
|
384
605
|
return _Record(fields, required)
|
|
385
606
|
|
|
386
607
|
|
|
608
|
+
def _class_annotation_shadowed(annotation: ast.expr, names: frozenset[str], context: PythonFileContext) -> bool:
|
|
609
|
+
pending = [(annotation, True)]
|
|
610
|
+
for _ in range(_MAX_EXPANSION):
|
|
611
|
+
if not pending:
|
|
612
|
+
return False
|
|
613
|
+
node, forward = pending.pop()
|
|
614
|
+
parsed = _string_expression(node) if forward else node
|
|
615
|
+
if parsed is None or (isinstance(parsed, ast.Name) and parsed.id in names):
|
|
616
|
+
return True
|
|
617
|
+
pending.extend(_class_annotation_children(parsed, forward=forward, context=context))
|
|
618
|
+
return True
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def _class_annotation_children(
|
|
622
|
+
node: ast.expr, *, forward: bool, context: PythonFileContext
|
|
623
|
+
) -> list[tuple[ast.expr, bool]]:
|
|
624
|
+
if not isinstance(node, ast.Subscript):
|
|
625
|
+
return [(child, forward) for child in ast.iter_child_nodes(node) if isinstance(child, ast.expr)]
|
|
626
|
+
members = _subscript_members(node)
|
|
627
|
+
symbol = context.module_imports.resolved_symbol(node.value, sources=TYPING_SOURCES)
|
|
628
|
+
if symbol == "Annotated" and members:
|
|
629
|
+
return [(node.value, False), (members[0], True)]
|
|
630
|
+
return [(node.value, False), *((member, symbol != "Literal") for member in members)]
|
|
631
|
+
|
|
632
|
+
|
|
387
633
|
def _record_required(node: ast.ClassDef, context: PythonFileContext, provenance: ResourceProvenance) -> bool | None:
|
|
388
634
|
if node.type_params:
|
|
389
635
|
return None
|
|
@@ -394,16 +640,16 @@ def _record_required(node: ast.ClassDef, context: PythonFileContext, provenance:
|
|
|
394
640
|
for decorator in node.decorator_list
|
|
395
641
|
if not (
|
|
396
642
|
_untouched(decorator, provenance)
|
|
397
|
-
and context.
|
|
643
|
+
and context.module_imports.resolves(decorator, sources=TYPING_SOURCES, symbol="final")
|
|
398
644
|
)
|
|
399
645
|
]
|
|
400
646
|
if decorators:
|
|
401
647
|
return _direct_dataclass(node, context, provenance, decorators)
|
|
402
648
|
if len(node.bases) != 1:
|
|
403
649
|
return None
|
|
404
|
-
if context.
|
|
650
|
+
if context.module_imports.resolves(node.bases[0], sources=MODEL_SOURCES, symbol="BaseModel"):
|
|
405
651
|
return True if not node.keywords else None
|
|
406
|
-
if not context.
|
|
652
|
+
if not context.module_imports.resolves(node.bases[0], sources=TYPING_SOURCES, symbol="TypedDict"):
|
|
407
653
|
return None
|
|
408
654
|
required = True
|
|
409
655
|
for keyword in node.keywords:
|
|
@@ -422,7 +668,7 @@ def _direct_dataclass(
|
|
|
422
668
|
return None
|
|
423
669
|
decorator = decorators[0]
|
|
424
670
|
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
425
|
-
if not _untouched(target, provenance) or not context.
|
|
671
|
+
if not _untouched(target, provenance) or not context.module_imports.resolves(
|
|
426
672
|
target, sources=_DATACLASS_SOURCES, symbol="dataclass"
|
|
427
673
|
):
|
|
428
674
|
return None
|
|
@@ -479,12 +725,91 @@ def _alias_name(node: ast.stmt) -> str | None:
|
|
|
479
725
|
return None
|
|
480
726
|
|
|
481
727
|
|
|
482
|
-
def
|
|
728
|
+
def _namespace_metadata_value(node: ast.ClassDef, attribute: str) -> ast.expr | None:
|
|
729
|
+
if node.bases or node.keywords or node.decorator_list or node.type_params or len(node.body) > _MAX_EXPANSION:
|
|
730
|
+
return None
|
|
731
|
+
values: list[tuple[str, ast.expr]] = []
|
|
732
|
+
for member in node.body:
|
|
733
|
+
binding = _value_binding(member)
|
|
734
|
+
if binding is not None and not binding[0].startswith("__"):
|
|
735
|
+
values.append(binding)
|
|
736
|
+
elif isinstance(member, ast.Pass | ast.Expr) and (
|
|
737
|
+
isinstance(member, ast.Pass)
|
|
738
|
+
or (isinstance(member.value, ast.Constant) and isinstance(member.value.value, str))
|
|
739
|
+
):
|
|
740
|
+
continue
|
|
741
|
+
else:
|
|
742
|
+
return None
|
|
743
|
+
if len(values) != 1 or values[0][0] != attribute:
|
|
744
|
+
return None
|
|
745
|
+
return values[0][1]
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def _metadata_tuple_index(node: ast.expr) -> int | None:
|
|
749
|
+
if isinstance(node, ast.Constant) and type(node.value) is int:
|
|
750
|
+
return node.value
|
|
751
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.operand, ast.Constant) and type(node.operand.value) is int:
|
|
752
|
+
if isinstance(node.op, ast.USub):
|
|
753
|
+
return -node.operand.value
|
|
754
|
+
if isinstance(node.op, ast.UAdd):
|
|
755
|
+
return node.operand.value
|
|
756
|
+
return None
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def _metadata_alias_graph(tree: ast.Module, bindings: dict[str, list[ast.AST]]) -> dict[str, set[str]]:
|
|
760
|
+
graph: dict[str, set[str]] = {}
|
|
761
|
+
for node in tree.body:
|
|
762
|
+
binding = _value_binding(node)
|
|
763
|
+
if binding is None:
|
|
764
|
+
continue
|
|
765
|
+
name, value = binding
|
|
766
|
+
if len(bindings.get(name, ())) != 1 or not isinstance(value, ast.Name):
|
|
767
|
+
continue
|
|
768
|
+
graph.setdefault(name, set()).add(value.id)
|
|
769
|
+
graph.setdefault(value.id, set()).add(name)
|
|
770
|
+
return graph
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
def _metadata_attribute_writes(
|
|
774
|
+
context: PythonFileContext, tree: ast.Module, provenance: ResourceProvenance
|
|
775
|
+
) -> dict[str, list[tuple[str, ast.expr]]]:
|
|
776
|
+
writes: dict[str, list[tuple[str, ast.expr]]] = {}
|
|
777
|
+
for target in context.nodes(ast.Attribute):
|
|
778
|
+
if not isinstance(target.ctx, ast.Store) or not isinstance(target.value, ast.Name):
|
|
779
|
+
continue
|
|
780
|
+
binding = provenance.binding(target.value.id, target)
|
|
781
|
+
if binding is None or provenance.scope(binding) is not tree or provenance.scope(target) is not tree:
|
|
782
|
+
continue
|
|
783
|
+
parent = context.parents.get(target)
|
|
784
|
+
if isinstance(parent, ast.Assign | ast.AnnAssign) and parent.value is not None:
|
|
785
|
+
writes.setdefault(target.value.id, []).append((target.attr, parent.value))
|
|
786
|
+
return writes
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def _value_binding(node: ast.stmt) -> tuple[str, ast.expr] | None:
|
|
790
|
+
match node:
|
|
791
|
+
case (
|
|
792
|
+
ast.Assign(targets=[ast.Name(id=name)], value=value)
|
|
793
|
+
| ast.AnnAssign(target=ast.Name(id=name), value=ast.expr() as value)
|
|
794
|
+
):
|
|
795
|
+
return name, value
|
|
796
|
+
case _:
|
|
797
|
+
return None
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def _alias_value(
|
|
801
|
+
node: ast.stmt, context: PythonFileContext, provenance: ResourceProvenance | None = None
|
|
802
|
+
) -> ast.expr | None:
|
|
483
803
|
match node:
|
|
484
804
|
case ast.TypeAlias(type_params=[]):
|
|
485
805
|
return node.value
|
|
486
806
|
case ast.AnnAssign(target=ast.Name(), annotation=annotation, value=value):
|
|
487
|
-
return
|
|
807
|
+
return (
|
|
808
|
+
value
|
|
809
|
+
if context.module_imports.resolves(annotation, sources=TYPING_SOURCES, symbol="TypeAlias")
|
|
810
|
+
and (provenance is None or _untouched(annotation, provenance))
|
|
811
|
+
else None
|
|
812
|
+
)
|
|
488
813
|
case ast.Assign(targets=[ast.Name()], value=value):
|
|
489
814
|
return None if isinstance(value, ast.Constant) and isinstance(value.value, str) else value
|
|
490
815
|
case _:
|
|
@@ -500,10 +825,93 @@ def _string_expression(node: ast.expr) -> ast.expr | None:
|
|
|
500
825
|
return None
|
|
501
826
|
|
|
502
827
|
|
|
828
|
+
def _scoped_module_mutations(provenance: ResourceProvenance, tree: ast.Module) -> set[str]:
|
|
829
|
+
original = provenance.mutated
|
|
830
|
+
if not original:
|
|
831
|
+
return original
|
|
832
|
+
bindings = provenance.bindings.get(tree, {})
|
|
833
|
+
owners = {name: bindings[name][0] for name in original if len(bindings.get(name, ())) == 1}
|
|
834
|
+
writes = _module_attribute_mutations(provenance, owners)
|
|
835
|
+
if writes is None:
|
|
836
|
+
return original
|
|
837
|
+
return {
|
|
838
|
+
name
|
|
839
|
+
for name in original
|
|
840
|
+
if name not in owners or name in writes or _module_binding_escaped(name, owners[name], provenance)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
def _module_attribute_mutations(provenance: ResourceProvenance, owners: dict[str, ast.AST]) -> set[str] | None:
|
|
845
|
+
changed: set[str] = set()
|
|
846
|
+
count = 0
|
|
847
|
+
for target in provenance.context.nodes(ast.Attribute):
|
|
848
|
+
if not isinstance(target.ctx, ast.Store | ast.Del):
|
|
849
|
+
continue
|
|
850
|
+
count += 1
|
|
851
|
+
if count > _MAX_EXPANSION:
|
|
852
|
+
return None
|
|
853
|
+
root: ast.expr = target
|
|
854
|
+
while isinstance(root, ast.Attribute):
|
|
855
|
+
root = root.value
|
|
856
|
+
if not isinstance(root, ast.Name) or root.id not in owners:
|
|
857
|
+
continue
|
|
858
|
+
binding = provenance.binding(root.id, target)
|
|
859
|
+
if binding is None or binding is owners[root.id]:
|
|
860
|
+
changed.add(root.id)
|
|
861
|
+
return changed
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
def _module_binding_escaped(name: str, owner: ast.AST, provenance: ResourceProvenance) -> bool:
|
|
865
|
+
reads = provenance.reads.get(name, ())
|
|
866
|
+
if len(reads) > _MAX_EXPANSION:
|
|
867
|
+
return True
|
|
868
|
+
return any(
|
|
869
|
+
provenance.binding(name, read) is owner and _module_read_escapes(read, provenance.context) for read in reads
|
|
870
|
+
)
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def _module_read_escapes(read: ast.Name, context: PythonFileContext) -> bool:
|
|
874
|
+
current: ast.AST = read
|
|
875
|
+
for _ in range(_MAX_EXPANSION):
|
|
876
|
+
parent = context.parents.get(current)
|
|
877
|
+
if parent is None:
|
|
878
|
+
return False
|
|
879
|
+
step = _module_escape_step(parent, current, context)
|
|
880
|
+
if step is not None:
|
|
881
|
+
return step
|
|
882
|
+
current = parent
|
|
883
|
+
return True
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def _module_escape_step(parent: ast.AST, current: ast.AST, context: PythonFileContext) -> bool | None:
|
|
887
|
+
match parent:
|
|
888
|
+
case ast.Call():
|
|
889
|
+
return parent.func is not current
|
|
890
|
+
case ast.Return() | ast.Yield() | ast.YieldFrom() | ast.NamedExpr():
|
|
891
|
+
return True
|
|
892
|
+
case ast.arguments():
|
|
893
|
+
return True if current in parent.defaults or current in parent.kw_defaults else None
|
|
894
|
+
case ast.Assign() | ast.AnnAssign() | ast.AugAssign():
|
|
895
|
+
value = parent.value
|
|
896
|
+
return value is not None and value is current and not _union_expression(value, context)
|
|
897
|
+
case ast.Subscript() if parent.value is not current:
|
|
898
|
+
symbol = context.module_imports.resolved_symbol(parent.value, sources=TYPING_SOURCES)
|
|
899
|
+
if symbol is None:
|
|
900
|
+
return True
|
|
901
|
+
case ast.withitem():
|
|
902
|
+
return parent.context_expr is current
|
|
903
|
+
case _:
|
|
904
|
+
pass
|
|
905
|
+
return False if isinstance(parent, ast.stmt) else None
|
|
906
|
+
|
|
907
|
+
|
|
503
908
|
def _untouched(node: ast.expr, provenance: ResourceProvenance) -> bool:
|
|
504
909
|
while isinstance(node, ast.Attribute):
|
|
505
910
|
node = node.value
|
|
506
|
-
|
|
911
|
+
if not isinstance(node, ast.Name) or node.id in provenance.mutated or node.id in provenance.external_bindings:
|
|
912
|
+
return False
|
|
913
|
+
binding = provenance.binding(node.id, node)
|
|
914
|
+
return binding is None or isinstance(binding, ast.alias)
|
|
507
915
|
|
|
508
916
|
|
|
509
917
|
def _union_expression(node: ast.expr, context: PythonFileContext) -> bool:
|
|
@@ -516,7 +924,7 @@ def _union_expression(node: ast.expr, context: PythonFileContext) -> bool:
|
|
|
516
924
|
return True
|
|
517
925
|
if not isinstance(node, ast.Subscript):
|
|
518
926
|
return False
|
|
519
|
-
symbol = context.
|
|
927
|
+
symbol = context.module_imports.resolved_symbol(node.value, sources=TYPING_SOURCES)
|
|
520
928
|
if symbol == "Union":
|
|
521
929
|
return True
|
|
522
930
|
if symbol == "Optional":
|
|
@@ -107,7 +107,7 @@ class PreferDiscriminatedModelUnion(Rule):
|
|
|
107
107
|
limitations=(
|
|
108
108
|
"Declared module-level unions of at least two source-visible direct dataclasses, Pydantic BaseModels or TypedDicts require a shared disjoint Literal field. PEP 604, typing Union, Optional, Annotated, forward references and bounded local aliases are supported; a None branch is allowed.",
|
|
109
109
|
"The explicit-tag policy is a modeling preference: nominal variants already support isinstance narrowing. Scalar/container unions, inline annotations, generic or inherited variants, behavior classes, unresolved variants and dynamic or ambiguous namespaces are excluded. Imported tag aliases are not resolved; an explicit literal field paired with an unresolved imported or local class domain abstains, as do opaque explicit Literal domains. Qualified imported domains and unsupported computed aliases remain uncertain rather than proving a missing tag.",
|
|
110
|
-
"String, bytes, integer, Boolean and None tag values are supported. Boolean and integer Literal values retain their distinct static types; use identity checks when equality would conflate their runtime values. A bounded hierarchy of shared literal fields may distinguish nested groups; overlapping tags without a further discriminator still warn. Nested Literal domains and transparent Final, ReadOnly, Annotated and local alias wrappers are supported. TypedDict tags must be required; dataclass ClassVar tags must have a value. Import-proven typing final decorators preserve record recognition.
|
|
110
|
+
"String, bytes, integer, Boolean and None tag values are supported. Boolean and integer Literal values retain their distinct static types; use identity checks when equality would conflate their runtime values. A bounded hierarchy of shared literal fields may distinguish nested groups; overlapping tags without a further discriminator still warn. Nested Literal domains and transparent Final, ReadOnly, Annotated and local alias wrappers are supported. TypedDict tags must be required; dataclass ClassVar tags must have a value. Direct import-proven InitVar annotations without defaults are constructor inputs rather than instance tags; quoted, wrapped, locally aliased or defaulted InitVar forms remain uncertain. Import-proven typing final decorators preserve record recognition. Explicit Pydantic callable Discriminators, bounded stable reusable metadata, single-value plain local class namespaces, literal immutable tuple projections and imported pydantic.types constructors are respected; source-proven string field names, including local bindings and concatenation, still need Literal fields. Source-proven string discriminator writes through stable aliases invalidate callable coverage; opaque mutation, helper escapes and excessive alias graphs abstain. Module bindings are independent of unrelated local parameters and their attribute writes; class annotation shadows and global rebinding remain excluded. Other runtime configuration and default correctness remain separate contracts. No automatic field addition is safe for every constructor or serializer.",
|
|
111
111
|
"For nullable-field dependencies, only stable direct module-level import-proven Pydantic BaseModel subclasses with pure direct after model validators are inspected.",
|
|
112
112
|
"Bounded paths may contain finite tag comparisons or literal-container membership, None identity tests, Boolean combinations, nested guards and returns of the same instance. Every rejection region must prove one tag-dependent nullable-field requirement; joint missing-field or independent-tag-axis conditions abstain.",
|
|
113
113
|
"Tags must use imported Literal domains, optionally composed through direct unions, with at least two distinct homogeneous string, integer or Boolean values, optionally including an explicit None state. Mixed primitive families, bool/int equality collisions, open or computed tags abstain. Payloads must explicitly include None in a union, imported Union or Optional.",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/_analysis_session.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.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.54.0 → sarj_python_lint-1.56.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|