sarj-python-lint 1.101.0__tar.gz → 1.101.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/PKG-INFO +1 -1
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/pyproject.toml +1 -1
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_opaque_contracts.py +12 -5
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +5 -2
- sarj_python_lint-1.101.1/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +378 -0
- sarj_python_lint-1.101.0/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -189
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/.gitignore +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/LICENSE +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/README.md +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_comments.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_prepared_sources.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_rule_descriptors.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rule_metadata.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_postgres.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_store_sql.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.101.0 → sarj_python_lint-1.101.1}/src/sarj_python_lint/suppression_policy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.101.
|
|
3
|
+
Version: 1.101.1
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
18
|
from sarj_python_lint._file_context import PythonFileContext
|
|
19
|
+
from sarj_python_lint.rules._local_source import LocalSourceFacts
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
type _Function = ast.FunctionDef | ast.AsyncFunctionDef
|
|
@@ -111,11 +112,13 @@ class OpaqueContractFacts:
|
|
|
111
112
|
context: PythonFileContext,
|
|
112
113
|
facts: MappingShapeFacts,
|
|
113
114
|
*,
|
|
115
|
+
local_source: LocalSourceFacts,
|
|
114
116
|
modules: dict[Path, OpaqueContractFacts] | None = None,
|
|
115
117
|
budget: _ProofBudget | None = None,
|
|
116
118
|
) -> None:
|
|
117
119
|
self.context = context
|
|
118
120
|
self.facts = facts
|
|
121
|
+
self.local_source = local_source
|
|
119
122
|
self.native = NativeObjectContracts(context, facts)
|
|
120
123
|
self.guards: dict[str, _Refinement | None] = {}
|
|
121
124
|
self.checked: dict[_Function, frozenset[ast.arg]] = {}
|
|
@@ -712,20 +715,24 @@ class OpaqueContractFacts:
|
|
|
712
715
|
|
|
713
716
|
def _imported_source(self, qualified: str) -> tuple[OpaqueContractFacts, ast.FunctionDef] | None:
|
|
714
717
|
module, _, name = qualified.rpartition(".")
|
|
715
|
-
path = self.context.session.
|
|
716
|
-
self.context.path, module, 0, facts=self.context.session.first_party
|
|
717
|
-
)
|
|
718
|
+
path = self.local_source.resolve_module(self.context.path, module, 0, facts=self.context.session.first_party)
|
|
718
719
|
if path is not None and (owner := self.modules.get(path.resolve())) is not None:
|
|
719
720
|
return owner.export(name)
|
|
720
721
|
if len(self.modules) >= _MAX_GUARDS:
|
|
721
722
|
return None
|
|
722
|
-
loaded = self.
|
|
723
|
+
loaded = self.local_source.read_module(path) if path is not None else None
|
|
723
724
|
if loaded is None:
|
|
724
725
|
return None
|
|
725
726
|
from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- source loading runs after AnalysisSession initialization.
|
|
726
727
|
|
|
727
728
|
context = PythonFileContext(loaded.path, loaded.source, self.context.session)
|
|
728
|
-
owner = OpaqueContractFacts(
|
|
729
|
+
owner = OpaqueContractFacts(
|
|
730
|
+
context,
|
|
731
|
+
MappingShapeFacts(context),
|
|
732
|
+
local_source=self.local_source,
|
|
733
|
+
modules=self.modules,
|
|
734
|
+
budget=self.budget,
|
|
735
|
+
)
|
|
729
736
|
return owner.export(name)
|
|
730
737
|
|
|
731
738
|
def export(self, name: str) -> tuple[OpaqueContractFacts, ast.FunctionDef] | None:
|
|
@@ -15,6 +15,7 @@ from sarj_python_lint.rule_base import (
|
|
|
15
15
|
RuleExample,
|
|
16
16
|
Severity,
|
|
17
17
|
)
|
|
18
|
+
from sarj_python_lint.rules._local_source import LocalSourceFacts
|
|
18
19
|
from sarj_python_lint.rules._native_object_contracts import builtin_object, builtin_symbol
|
|
19
20
|
from sarj_python_lint.rules._opaque_contracts import REFINEMENT_TYPES, OpaqueContractFacts
|
|
20
21
|
from sarj_python_lint.rules.require_pydantic_for_mapping_shape import MappingShapeFacts
|
|
@@ -48,7 +49,7 @@ class PreferPreciseObjectContract(Rule):
|
|
|
48
49
|
"Safe fixes narrow only a bare builtin object return annotation to builtin str for a closed private nested synchronous helper returning one exact string literal, with every call used directly as an if test and all containing return contracts explicit. Existing inert/metadata/binding proofs must hold; public/domain/container/parameter/model contracts and other result uses remain review-only.",
|
|
49
50
|
"Checks builtin object in function signatures and class field annotations, including supported containers, callables, optional unions and quoted annotations. Local opaque values, object() sentinels, Literal/Annotated metadata, framework hooks, proven override signatures, and registered raw Pydantic callbacks are excluded. Functions that directly validate an object parameter through a source-visible BaseModel, BaseSettings or RootModel model_validate call, positionally or with obj=, retain that raw boundary. JSON text inputs still require a text or byte contract. Aliases must resolve to their source declaration and their bodies use definition scope. Alias graphs are bounded and shared branches reuse their facts. Imported or shadowed application types named object are preserved. Genuine opaque public contracts can use an explained exact-code exception; narrowing outside the exact private truthiness-only subset remains manual.",
|
|
50
51
|
"An import-proven TypeGuard or TypeIs retains bare object on its guarded positional parameter and unchecked object slots in its declared refinement result; other object parameters remain checked. Guard correctness belongs to the type-guard rules. Universal type[object] class contracts and the return of a canonical module-level PEP 562 __getattr__ hook retain their opaque runtime contracts.",
|
|
51
|
-
"Ordinary raw validators retain object inputs tied to builtin isinstance, exact builtin type comparisons or an actual source-resolved TypeGuard/TypeIs through rejecting branches, actual type/refinement assertions, accepting terminal branches with a rejecting fallback, or conditional expressions. Stable projections from containers with unchecked value slots retain their raw root contract when those values are validated. Source-visible local and imported validators may delegate those inputs and raw results; precise result narrowing does not justify a bare object output. Related unchecked mapping projections and transforms retain unknown values; import-proven native parsers and JSONDecoder preserve raw outputs. Universal builtin and stdlib interoperation may retain raw inputs. Independent object parameters, domain constructors and unproven parser names remain checked. Source resolution and dataflow
|
|
52
|
+
"Ordinary raw validators retain object inputs tied to builtin isinstance, exact builtin type comparisons or an actual source-resolved TypeGuard/TypeIs through rejecting branches, actual type/refinement assertions, accepting terminal branches with a rejecting fallback, or conditional expressions. Stable projections from containers with unchecked value slots retain their raw root contract when those values are validated. Source-visible local and imported validators may delegate those inputs and raw results; precise result narrowing does not justify a bare object output. Related unchecked mapping projections and transforms retain unknown values; import-proven native parsers and JSONDecoder preserve raw outputs. Universal builtin and stdlib interoperation may retain raw inputs. Independent object parameters, domain constructors and unproven parser names remain checked. Source resolution and dataflow use a bounded per-file proof cache, captured source inputs and cycle guards; rebinding, decorators and namespace mutation invalidate delegated proof.",
|
|
52
53
|
"Native opaque contracts include canonical ast.Constant.value reads and captures, FunctionType invocations, identity-only inputs and sys.getrefcount. Actual exec/eval globals and locals retain unknown values only through bounded native dict construction and alias flow; unrelated dictionaries and source domain factories do not borrow that ownership. Stub property returns on an import-proven runtime_checkable Protocol retain unchecked incoming attributes. Generic callable results, ordinary Protocol methods, application properties and unused parameters remain checked.",
|
|
53
54
|
"Raw input unions may include scalar or None branches; only genuine unchecked container branches seed raw projections. Mapping consumers that inspect only keys retain uninspected value slots. Projected aliases require simple statement dominance, and precise active guards permit typed consumption after validation. Native namespace output reads require a preceding direct function-scope execution and abstain after authored mutations. Benign builtins imports do not hide builtin object contracts; explicit object shadowing and source-visible object mutations remain outside scope.",
|
|
54
55
|
"An import-proven Iterable of unchecked values retains its raw element contract when every input use is a direct synchronous iteration and every reachable element read directly enters source-proven Pydantic model_validate. Deferred closures, rebinding, other iterator/element uses, unknown model providers and JSON-text parsers do not establish that boundary.",
|
|
@@ -131,7 +132,9 @@ class PreferPreciseObjectContract(Rule):
|
|
|
131
132
|
if facts.provenance.wildcard:
|
|
132
133
|
return []
|
|
133
134
|
findings: list[Diagnostic] = []
|
|
134
|
-
opaque = OpaqueContractFacts(
|
|
135
|
+
opaque = OpaqueContractFacts(
|
|
136
|
+
context, facts, local_source=LocalSourceFacts(source_inputs=context.session.source_inputs)
|
|
137
|
+
)
|
|
135
138
|
for owner, annotation in _contracts(context):
|
|
136
139
|
if not _has_object(annotation, context, facts, at=annotation):
|
|
137
140
|
continue
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import PurePosixPath
|
|
6
|
+
from typing import TYPE_CHECKING, final, override
|
|
7
|
+
|
|
8
|
+
from sarj_python_lint.rule_base import (
|
|
9
|
+
AutofixPolicy,
|
|
10
|
+
Diagnostic,
|
|
11
|
+
ExampleFile,
|
|
12
|
+
ExampleOutcome,
|
|
13
|
+
Rule,
|
|
14
|
+
RuleCategory,
|
|
15
|
+
RuleDocumentation,
|
|
16
|
+
RuleExample,
|
|
17
|
+
Severity,
|
|
18
|
+
is_suppressed,
|
|
19
|
+
)
|
|
20
|
+
from sarj_python_lint.rules._local_source import LocalSourceFacts
|
|
21
|
+
from sarj_python_lint.rules.require_pydantic_for_mapping_shape import MappingShapeFacts
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
28
|
+
|
|
29
|
+
_UNTYPED = frozenset(
|
|
30
|
+
{
|
|
31
|
+
"typing.Any",
|
|
32
|
+
"typing_extensions.Any",
|
|
33
|
+
"typing.Dict",
|
|
34
|
+
"typing.Mapping",
|
|
35
|
+
"typing.MutableMapping",
|
|
36
|
+
"collections.abc.Mapping",
|
|
37
|
+
"collections.abc.MutableMapping",
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
_JSON_PARSERS = frozenset({"json.load", "json.loads"})
|
|
41
|
+
_MAX_DEPTH = 32
|
|
42
|
+
_BAD = "def service_name(state: dict[str, object]):\n return state.get('name')\n"
|
|
43
|
+
_GOOD = "from typing import TypedDict\nclass State(TypedDict):\n name: str\ndef service_name(state: State) -> str | None:\n return state.get('name')\n"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@final
|
|
47
|
+
class PreferTypedMappingGet(Rule):
|
|
48
|
+
id = "prefer-typed-mapping-get"
|
|
49
|
+
code = "SARJ535"
|
|
50
|
+
documentation = RuleDocumentation(
|
|
51
|
+
default_level=Severity.WARNING,
|
|
52
|
+
summary="Read named fields from typed records instead of schema-free mappings.",
|
|
53
|
+
rationale="A get call on an Any/object-valued mapping or unchecked decoded JSON does not declare the field's type or distinguish a misspelled field from missing data. A default can conceal that gap.",
|
|
54
|
+
remediation="Validate external data into a named Pydantic model before reading its attributes. For trusted internal records, declare a TypedDict or another precise record. Preserve legitimate get calls on typed mappings, optional TypedDict fields and validated normalization callbacks.",
|
|
55
|
+
category=RuleCategory.MAINTAINABILITY,
|
|
56
|
+
autofix=AutofixPolicy.NONE,
|
|
57
|
+
limitations=(
|
|
58
|
+
"Checks get calls on string mappings annotated with object, Any or JsonValue values, bare standard dict/Mapping annotations, explicit Any/object receivers and import-proven json.load/loads results. Source-visible undecorated local or imported function returns, stable aliases, relative imports, reexports, projections and chained gets retain raw provenance through bounded resolution. Unannotated helpers are followed when every return is proven schema-free; precise return annotations retain their contract.",
|
|
59
|
+
"Precise mappings and TypedDicts, arbitrary unannotated parameters, application methods named get, unresolved external return types, ambiguous assignments and custom JSON object hooks abstain. Imported helpers are parsed without executing them, using a bounded per-file resolver that shares captured source inputs; dynamic or mutated namespaces, decorated functions and recursive or unresolved returns abstain. This syntax analysis does not replace the type checker.",
|
|
60
|
+
"Registered Pydantic raw normalization callbacks retain their validation boundary. Generated/vendor sources and wildcard or dynamic namespaces are excluded. No automatic rewrite because validation, defaults, missing keys and error handling can change behavior.",
|
|
61
|
+
"Existing mapping-shape and external-boundary diagnostics take precedence at the same read when selected together; selecting this rule alone still reports it.",
|
|
62
|
+
"Chained reads with the same starting position produce one actionable warning for their raw receiver.",
|
|
63
|
+
),
|
|
64
|
+
examples=(
|
|
65
|
+
RuleExample(
|
|
66
|
+
example_id="opaque-get",
|
|
67
|
+
title="The field has no declared type",
|
|
68
|
+
outcome=ExampleOutcome.MATCH,
|
|
69
|
+
files=(ExampleFile.python("app/state.py", _BAD),),
|
|
70
|
+
focus_path=PurePosixPath("app/state.py"),
|
|
71
|
+
expected_count=1,
|
|
72
|
+
public=True,
|
|
73
|
+
),
|
|
74
|
+
RuleExample(
|
|
75
|
+
example_id="typed-get",
|
|
76
|
+
title="TypedDict retains the field contract",
|
|
77
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
78
|
+
files=(ExampleFile.python("app/state.py", _GOOD),),
|
|
79
|
+
focus_path=PurePosixPath("app/state.py"),
|
|
80
|
+
expected_count=0,
|
|
81
|
+
public=True,
|
|
82
|
+
),
|
|
83
|
+
RuleExample(
|
|
84
|
+
example_id="unchecked-json",
|
|
85
|
+
scenario="decoded-json",
|
|
86
|
+
title="Decoded JSON still needs validation",
|
|
87
|
+
outcome=ExampleOutcome.MATCH,
|
|
88
|
+
files=(
|
|
89
|
+
ExampleFile.python(
|
|
90
|
+
"app/state.py",
|
|
91
|
+
"import json\ndef name(text: str):\n state = json.loads(text)\n return state.get('name')\n",
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
focus_path=PurePosixPath("app/state.py"),
|
|
95
|
+
expected_count=1,
|
|
96
|
+
public=True,
|
|
97
|
+
),
|
|
98
|
+
RuleExample(
|
|
99
|
+
example_id="validated-json",
|
|
100
|
+
scenario="decoded-json",
|
|
101
|
+
title="Validate JSON before reading its fields",
|
|
102
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
103
|
+
files=(
|
|
104
|
+
ExampleFile.python(
|
|
105
|
+
"app/state.py",
|
|
106
|
+
"from pydantic import BaseModel\nclass State(BaseModel):\n name: str\ndef name(text: str) -> str:\n return State.model_validate_json(text).name\n",
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
focus_path=PurePosixPath("app/state.py"),
|
|
110
|
+
expected_count=0,
|
|
111
|
+
public=True,
|
|
112
|
+
),
|
|
113
|
+
RuleExample(
|
|
114
|
+
example_id="imported-parser-contract",
|
|
115
|
+
scenario="imported-return",
|
|
116
|
+
title="Imported parsing helpers keep their raw mapping contract",
|
|
117
|
+
outcome=ExampleOutcome.MATCH,
|
|
118
|
+
files=(
|
|
119
|
+
ExampleFile.python("app/__init__.py", "# package\n"),
|
|
120
|
+
ExampleFile.python(
|
|
121
|
+
"app/parser.py",
|
|
122
|
+
"import json\ndef parse(text: str) -> dict[str, object]:\n return json.loads(text)\n",
|
|
123
|
+
),
|
|
124
|
+
ExampleFile.python(
|
|
125
|
+
"app/state.py",
|
|
126
|
+
"from .parser import parse\ndef name(text: str):\n return parse(text).get('name')\n",
|
|
127
|
+
),
|
|
128
|
+
),
|
|
129
|
+
focus_path=PurePosixPath("app/state.py"),
|
|
130
|
+
expected_count=1,
|
|
131
|
+
public=True,
|
|
132
|
+
),
|
|
133
|
+
RuleExample(
|
|
134
|
+
example_id="imported-validated-model",
|
|
135
|
+
scenario="imported-return",
|
|
136
|
+
title="An imported parser validates a named model",
|
|
137
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
138
|
+
files=(
|
|
139
|
+
ExampleFile.python("app/__init__.py", "# package\n"),
|
|
140
|
+
ExampleFile.python(
|
|
141
|
+
"app/parser.py",
|
|
142
|
+
"from pydantic import BaseModel\nclass State(BaseModel):\n labels: dict[str, str]\n"
|
|
143
|
+
"def parse(text: str) -> State:\n return State.model_validate_json(text)\n",
|
|
144
|
+
),
|
|
145
|
+
ExampleFile.python(
|
|
146
|
+
"app/state.py",
|
|
147
|
+
"from .parser import parse\ndef name(text: str):\n return parse(text).labels.get('name')\n",
|
|
148
|
+
),
|
|
149
|
+
),
|
|
150
|
+
focus_path=PurePosixPath("app/state.py"),
|
|
151
|
+
expected_count=0,
|
|
152
|
+
public=True,
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
description = documentation.summary
|
|
157
|
+
|
|
158
|
+
@override
|
|
159
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
160
|
+
if context.tree is None or context.generated or "get" not in context.symbol_source:
|
|
161
|
+
return []
|
|
162
|
+
facts = MappingShapeFacts(context)
|
|
163
|
+
if facts.provenance.wildcard or facts.provenance.dynamic_namespace:
|
|
164
|
+
return []
|
|
165
|
+
contracts = _ReturnContracts(facts)
|
|
166
|
+
findings = [
|
|
167
|
+
Diagnostic(
|
|
168
|
+
context.path,
|
|
169
|
+
call.lineno,
|
|
170
|
+
call.col_offset + 1,
|
|
171
|
+
self.code,
|
|
172
|
+
"Validate this schema-free mapping into a named model or use a precise internal record before reading fields with get.",
|
|
173
|
+
severity=Severity.WARNING,
|
|
174
|
+
)
|
|
175
|
+
for call in context.nodes(ast.Call)
|
|
176
|
+
if isinstance(call.func, ast.Attribute)
|
|
177
|
+
and call.func.attr == "get"
|
|
178
|
+
and not facts.raw_boundary(call)
|
|
179
|
+
and _untyped_receiver(call.func.value, facts, contracts)
|
|
180
|
+
and not is_suppressed(context.source_lines, call.lineno, self.code)
|
|
181
|
+
]
|
|
182
|
+
return list({(finding.line, finding.col): finding for finding in findings}.values())
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass(frozen=True, slots=True)
|
|
186
|
+
class _FunctionReturn:
|
|
187
|
+
owner: MappingShapeFacts
|
|
188
|
+
definition: ast.FunctionDef | ast.AsyncFunctionDef
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass(frozen=True, slots=True)
|
|
192
|
+
class _ImportLocation:
|
|
193
|
+
module: str
|
|
194
|
+
level: int
|
|
195
|
+
name: str
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class _ReturnContracts:
|
|
199
|
+
def __init__(self, facts: MappingShapeFacts) -> None:
|
|
200
|
+
self.owners = {facts.context.path: facts}
|
|
201
|
+
# Return lookups must leave other rules enough budget to resolve their validators.
|
|
202
|
+
self.local_source = LocalSourceFacts(source_inputs=facts.context.session.source_inputs)
|
|
203
|
+
|
|
204
|
+
def function(
|
|
205
|
+
self, expression: ast.expr, facts: MappingShapeFacts, seen: frozenset[ast.AST]
|
|
206
|
+
) -> _FunctionReturn | None:
|
|
207
|
+
if len(seen) >= _MAX_DEPTH or facts.provenance.wildcard or facts.provenance.dynamic_namespace:
|
|
208
|
+
return None
|
|
209
|
+
attributes: list[str] = []
|
|
210
|
+
root = expression
|
|
211
|
+
while isinstance(root, ast.Attribute):
|
|
212
|
+
attributes.append(root.attr)
|
|
213
|
+
root = root.value
|
|
214
|
+
if (
|
|
215
|
+
not isinstance(root, ast.Name)
|
|
216
|
+
or facts.shadowed(root.id, expression)
|
|
217
|
+
or root.id in facts.provenance.mutated | facts.provenance.external_bindings
|
|
218
|
+
):
|
|
219
|
+
return None
|
|
220
|
+
binding = facts.provenance.binding(root.id, expression)
|
|
221
|
+
if binding is None or binding in seen:
|
|
222
|
+
return None
|
|
223
|
+
match binding:
|
|
224
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef() if not attributes:
|
|
225
|
+
return _FunctionReturn(facts, binding) if not binding.decorator_list else None
|
|
226
|
+
case ast.Name() if not attributes:
|
|
227
|
+
value = facts.provenance.assignment_value(binding, expression)
|
|
228
|
+
return (
|
|
229
|
+
self.function(value, facts, seen | {binding})
|
|
230
|
+
if isinstance(value, ast.Name | ast.Attribute)
|
|
231
|
+
else None
|
|
232
|
+
)
|
|
233
|
+
case ast.alias():
|
|
234
|
+
return self.imported(binding, tuple(reversed(attributes)), facts, seen)
|
|
235
|
+
case _:
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
def imported(
|
|
239
|
+
self, binding: ast.alias, attributes: tuple[str, ...], facts: MappingShapeFacts, seen: frozenset[ast.AST]
|
|
240
|
+
) -> _FunctionReturn | None:
|
|
241
|
+
if len(seen) >= _MAX_DEPTH or binding in seen:
|
|
242
|
+
return None
|
|
243
|
+
location = _import_location(binding, attributes, facts)
|
|
244
|
+
if location is None:
|
|
245
|
+
return None
|
|
246
|
+
path = self.local_source.resolve_module(
|
|
247
|
+
facts.context.path, location.module, location.level, facts=facts.context.session.first_party
|
|
248
|
+
)
|
|
249
|
+
if path is None or _namespace_uncertain(facts, path):
|
|
250
|
+
return None
|
|
251
|
+
owner = self._owner(path, facts)
|
|
252
|
+
if (
|
|
253
|
+
owner is None
|
|
254
|
+
or owner.context.tree is None
|
|
255
|
+
or owner.provenance.wildcard
|
|
256
|
+
or owner.provenance.dynamic_namespace
|
|
257
|
+
):
|
|
258
|
+
return None
|
|
259
|
+
bindings = owner.provenance.bindings.get(owner.context.tree, {}).get(location.name, ())
|
|
260
|
+
if len(bindings) != 1 or location.name in owner.provenance.mutated | owner.provenance.external_bindings:
|
|
261
|
+
return None
|
|
262
|
+
[target] = bindings
|
|
263
|
+
following = seen | {binding}
|
|
264
|
+
match target:
|
|
265
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef():
|
|
266
|
+
result = _FunctionReturn(owner, target) if not target.decorator_list else None
|
|
267
|
+
case ast.alias():
|
|
268
|
+
result = self.imported(target, (), owner, following)
|
|
269
|
+
case _:
|
|
270
|
+
result = None
|
|
271
|
+
return result if result is not None and not _namespace_uncertain(facts, result.owner.context.path) else None
|
|
272
|
+
|
|
273
|
+
def _owner(self, path: Path, facts: MappingShapeFacts) -> MappingShapeFacts | None:
|
|
274
|
+
if path not in self.owners:
|
|
275
|
+
if len(self.owners) >= _MAX_DEPTH:
|
|
276
|
+
return None
|
|
277
|
+
module = self.local_source.read_module(path)
|
|
278
|
+
if module is None:
|
|
279
|
+
return None
|
|
280
|
+
from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- source owners are constructed after analysis-session initialization.
|
|
281
|
+
|
|
282
|
+
self.owners[path] = MappingShapeFacts(PythonFileContext(path, module.source, facts.context.session))
|
|
283
|
+
return self.owners[path]
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _import_location(
|
|
287
|
+
binding: ast.alias, attributes: tuple[str, ...], facts: MappingShapeFacts
|
|
288
|
+
) -> _ImportLocation | None:
|
|
289
|
+
parent = facts.context.parents.get(binding)
|
|
290
|
+
match parent:
|
|
291
|
+
case ast.ImportFrom(module=module, level=level):
|
|
292
|
+
parts = (*((module,) if module else ()), *((binding.name, *attributes[:-1]) if attributes else ()))
|
|
293
|
+
return _ImportLocation(".".join(parts), level, attributes[-1] if attributes else binding.name)
|
|
294
|
+
case ast.Import() if attributes:
|
|
295
|
+
prefix = binding.name if binding.asname else binding.name.partition(".")[0]
|
|
296
|
+
return _ImportLocation(".".join((prefix, *attributes[:-1])), 0, attributes[-1])
|
|
297
|
+
case _:
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _namespace_uncertain(facts: MappingShapeFacts, path: Path) -> bool:
|
|
302
|
+
if facts.provenance.mock_namespace_patching:
|
|
303
|
+
return True
|
|
304
|
+
target = path.resolve()
|
|
305
|
+
return any(
|
|
306
|
+
target == source or (source.name == "__init__.py" and target.is_relative_to(source.parent))
|
|
307
|
+
for source in facts.provenance.imported_namespace_hazards
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _untyped_receiver(
|
|
312
|
+
node: ast.expr, facts: MappingShapeFacts, contracts: _ReturnContracts, *, seen: frozenset[ast.AST] = frozenset()
|
|
313
|
+
) -> bool:
|
|
314
|
+
if len(seen) >= _MAX_DEPTH:
|
|
315
|
+
return False
|
|
316
|
+
if facts.mapping(node):
|
|
317
|
+
return True
|
|
318
|
+
match node:
|
|
319
|
+
case ast.Name():
|
|
320
|
+
return _name_receiver(node, facts, contracts, seen)
|
|
321
|
+
case ast.Call(func=func, keywords=keywords) if facts.qualified(func, node) in _JSON_PARSERS:
|
|
322
|
+
return not any(keyword.arg in {None, "object_hook", "object_pairs_hook", "cls"} for keyword in keywords)
|
|
323
|
+
case ast.Call(func=ast.Attribute(value=receiver, attr="get")) | ast.Subscript(value=receiver):
|
|
324
|
+
return _untyped_receiver(receiver, facts, contracts, seen=seen | {node})
|
|
325
|
+
case ast.Await(value=ast.Call() as call):
|
|
326
|
+
return _returned_mapping(call, facts, contracts, seen, awaited=True)
|
|
327
|
+
case ast.Call():
|
|
328
|
+
return _returned_mapping(node, facts, contracts, seen, awaited=False)
|
|
329
|
+
case _:
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _returned_mapping(
|
|
334
|
+
call: ast.Call, facts: MappingShapeFacts, contracts: _ReturnContracts, seen: frozenset[ast.AST], *, awaited: bool
|
|
335
|
+
) -> bool:
|
|
336
|
+
resolved = contracts.function(call.func, facts, seen)
|
|
337
|
+
if resolved is None:
|
|
338
|
+
return False
|
|
339
|
+
owner, function = resolved.owner, resolved.definition
|
|
340
|
+
if function in seen or isinstance(function, ast.AsyncFunctionDef) != awaited:
|
|
341
|
+
return False
|
|
342
|
+
if function.returns is not None:
|
|
343
|
+
return owner.annotation(function.returns, function.returns, mapping=True) or _erased_annotation(
|
|
344
|
+
function.returns, owner
|
|
345
|
+
)
|
|
346
|
+
returned = [node for node in owner.context.nodes(ast.Return) if owner.provenance.scope(node) is function]
|
|
347
|
+
return bool(returned) and all(
|
|
348
|
+
node.value is not None and _untyped_receiver(node.value, owner, contracts, seen=seen | {function})
|
|
349
|
+
for node in returned
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _name_receiver(
|
|
354
|
+
node: ast.Name, facts: MappingShapeFacts, contracts: _ReturnContracts, seen: frozenset[ast.AST]
|
|
355
|
+
) -> bool:
|
|
356
|
+
if node.id in facts.provenance.external_bindings:
|
|
357
|
+
return False
|
|
358
|
+
binding = facts.provenance.binding(node.id, node)
|
|
359
|
+
if binding is None or binding in seen:
|
|
360
|
+
return False
|
|
361
|
+
if isinstance(binding, ast.arg):
|
|
362
|
+
return binding.annotation is not None and _erased_annotation(binding.annotation, facts)
|
|
363
|
+
parent = facts.context.parents.get(binding)
|
|
364
|
+
if isinstance(parent, ast.AnnAssign):
|
|
365
|
+
return _erased_annotation(parent.annotation, facts)
|
|
366
|
+
value = facts.provenance.assignment_value(binding, node) if isinstance(binding, ast.Name) else None
|
|
367
|
+
return value is not None and _untyped_receiver(value, facts, contracts, seen=seen | {binding})
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _erased_annotation(node: ast.expr, facts: MappingShapeFacts) -> bool:
|
|
371
|
+
resolved = facts.resolve_annotation(node)
|
|
372
|
+
if resolved is None:
|
|
373
|
+
return False
|
|
374
|
+
return (
|
|
375
|
+
facts.builtin(resolved.expression, "dict", resolved.at)
|
|
376
|
+
or facts.builtin(resolved.expression, "object", resolved.at)
|
|
377
|
+
or facts.qualified(resolved.expression, resolved.at) in _UNTYPED
|
|
378
|
+
)
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import ast
|
|
4
|
-
from pathlib import PurePosixPath
|
|
5
|
-
from typing import TYPE_CHECKING, final, override
|
|
6
|
-
|
|
7
|
-
from sarj_python_lint.rule_base import (
|
|
8
|
-
AutofixPolicy,
|
|
9
|
-
Diagnostic,
|
|
10
|
-
ExampleFile,
|
|
11
|
-
ExampleOutcome,
|
|
12
|
-
Rule,
|
|
13
|
-
RuleCategory,
|
|
14
|
-
RuleDocumentation,
|
|
15
|
-
RuleExample,
|
|
16
|
-
Severity,
|
|
17
|
-
is_suppressed,
|
|
18
|
-
)
|
|
19
|
-
from sarj_python_lint.rules.require_pydantic_for_mapping_shape import MappingShapeFacts
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if TYPE_CHECKING:
|
|
23
|
-
from sarj_python_lint._file_context import PythonFileContext
|
|
24
|
-
|
|
25
|
-
_UNTYPED = frozenset(
|
|
26
|
-
{
|
|
27
|
-
"typing.Any",
|
|
28
|
-
"typing_extensions.Any",
|
|
29
|
-
"typing.Dict",
|
|
30
|
-
"typing.Mapping",
|
|
31
|
-
"typing.MutableMapping",
|
|
32
|
-
"collections.abc.Mapping",
|
|
33
|
-
"collections.abc.MutableMapping",
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
_JSON_PARSERS = frozenset({"json.load", "json.loads"})
|
|
37
|
-
_MAX_DEPTH = 32
|
|
38
|
-
_BAD = "def service_name(state: dict[str, object]):\n return state.get('name')\n"
|
|
39
|
-
_GOOD = "from typing import TypedDict\nclass State(TypedDict):\n name: str\ndef service_name(state: State) -> str | None:\n return state.get('name')\n"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@final
|
|
43
|
-
class PreferTypedMappingGet(Rule):
|
|
44
|
-
id = "prefer-typed-mapping-get"
|
|
45
|
-
code = "SARJ535"
|
|
46
|
-
documentation = RuleDocumentation(
|
|
47
|
-
default_level=Severity.WARNING,
|
|
48
|
-
summary="Read named fields from typed records instead of schema-free mappings.",
|
|
49
|
-
rationale="A get call on an Any/object-valued mapping or unchecked decoded JSON does not declare the field's type or distinguish a misspelled field from missing data. A default can conceal that gap.",
|
|
50
|
-
remediation="Validate external data into a named Pydantic model before reading its attributes. For trusted internal records, declare a TypedDict or another precise record. Preserve legitimate get calls on typed mappings, optional TypedDict fields and validated normalization callbacks.",
|
|
51
|
-
category=RuleCategory.MAINTAINABILITY,
|
|
52
|
-
autofix=AutofixPolicy.NONE,
|
|
53
|
-
limitations=(
|
|
54
|
-
"Checks get calls on string mappings annotated with object, Any or JsonValue values, bare standard dict/Mapping annotations, explicit Any/object receivers and import-proven json.load/loads results. Source-visible undecorated function return annotations, stable local aliases, projections and chained gets retain raw provenance through bounded resolution.",
|
|
55
|
-
"Precise mappings and TypedDicts, arbitrary unannotated parameters, application methods named get, unresolved external return types, ambiguous assignments and custom JSON object hooks abstain. This syntax analysis does not replace the type checker.",
|
|
56
|
-
"Registered Pydantic raw normalization callbacks retain their validation boundary. Generated/vendor sources and wildcard or dynamic namespaces are excluded. No automatic rewrite because validation, defaults, missing keys and error handling can change behavior.",
|
|
57
|
-
"Existing mapping-shape and external-boundary diagnostics take precedence at the same read when selected together; selecting this rule alone still reports it.",
|
|
58
|
-
"Chained reads with the same starting position produce one actionable warning for their raw receiver.",
|
|
59
|
-
),
|
|
60
|
-
examples=(
|
|
61
|
-
RuleExample(
|
|
62
|
-
example_id="opaque-get",
|
|
63
|
-
title="The field has no declared type",
|
|
64
|
-
outcome=ExampleOutcome.MATCH,
|
|
65
|
-
files=(ExampleFile.python("app/state.py", _BAD),),
|
|
66
|
-
focus_path=PurePosixPath("app/state.py"),
|
|
67
|
-
expected_count=1,
|
|
68
|
-
public=True,
|
|
69
|
-
),
|
|
70
|
-
RuleExample(
|
|
71
|
-
example_id="typed-get",
|
|
72
|
-
title="TypedDict retains the field contract",
|
|
73
|
-
outcome=ExampleOutcome.NO_MATCH,
|
|
74
|
-
files=(ExampleFile.python("app/state.py", _GOOD),),
|
|
75
|
-
focus_path=PurePosixPath("app/state.py"),
|
|
76
|
-
expected_count=0,
|
|
77
|
-
public=True,
|
|
78
|
-
),
|
|
79
|
-
RuleExample(
|
|
80
|
-
example_id="unchecked-json",
|
|
81
|
-
scenario="decoded-json",
|
|
82
|
-
title="Decoded JSON still needs validation",
|
|
83
|
-
outcome=ExampleOutcome.MATCH,
|
|
84
|
-
files=(
|
|
85
|
-
ExampleFile.python(
|
|
86
|
-
"app/state.py",
|
|
87
|
-
"import json\ndef name(text: str):\n state = json.loads(text)\n return state.get('name')\n",
|
|
88
|
-
),
|
|
89
|
-
),
|
|
90
|
-
focus_path=PurePosixPath("app/state.py"),
|
|
91
|
-
expected_count=1,
|
|
92
|
-
public=True,
|
|
93
|
-
),
|
|
94
|
-
RuleExample(
|
|
95
|
-
example_id="validated-json",
|
|
96
|
-
scenario="decoded-json",
|
|
97
|
-
title="Validate JSON before reading its fields",
|
|
98
|
-
outcome=ExampleOutcome.NO_MATCH,
|
|
99
|
-
files=(
|
|
100
|
-
ExampleFile.python(
|
|
101
|
-
"app/state.py",
|
|
102
|
-
"from pydantic import BaseModel\nclass State(BaseModel):\n name: str\ndef name(text: str) -> str:\n return State.model_validate_json(text).name\n",
|
|
103
|
-
),
|
|
104
|
-
),
|
|
105
|
-
focus_path=PurePosixPath("app/state.py"),
|
|
106
|
-
expected_count=0,
|
|
107
|
-
public=True,
|
|
108
|
-
),
|
|
109
|
-
),
|
|
110
|
-
)
|
|
111
|
-
description = documentation.summary
|
|
112
|
-
|
|
113
|
-
@override
|
|
114
|
-
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
115
|
-
if context.tree is None or context.generated or "get" not in context.symbol_source:
|
|
116
|
-
return []
|
|
117
|
-
facts = MappingShapeFacts(context)
|
|
118
|
-
if facts.provenance.wildcard or facts.provenance.dynamic_namespace:
|
|
119
|
-
return []
|
|
120
|
-
findings = [
|
|
121
|
-
Diagnostic(
|
|
122
|
-
context.path,
|
|
123
|
-
call.lineno,
|
|
124
|
-
call.col_offset + 1,
|
|
125
|
-
self.code,
|
|
126
|
-
"Validate this schema-free mapping into a named model or use a precise internal record before reading fields with get.",
|
|
127
|
-
severity=Severity.WARNING,
|
|
128
|
-
)
|
|
129
|
-
for call in context.nodes(ast.Call)
|
|
130
|
-
if isinstance(call.func, ast.Attribute)
|
|
131
|
-
and call.func.attr == "get"
|
|
132
|
-
and not facts.raw_boundary(call)
|
|
133
|
-
and _untyped_receiver(call.func.value, facts)
|
|
134
|
-
and not is_suppressed(context.source_lines, call.lineno, self.code)
|
|
135
|
-
]
|
|
136
|
-
return list({(finding.line, finding.col): finding for finding in findings}.values())
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def _untyped_receiver(node: ast.expr, facts: MappingShapeFacts, *, seen: frozenset[ast.AST] = frozenset()) -> bool:
|
|
140
|
-
if len(seen) >= _MAX_DEPTH:
|
|
141
|
-
return False
|
|
142
|
-
if facts.mapping(node):
|
|
143
|
-
return True
|
|
144
|
-
match node:
|
|
145
|
-
case ast.Name():
|
|
146
|
-
return _name_receiver(node, facts, seen)
|
|
147
|
-
case ast.Call(func=func, keywords=keywords) if facts.qualified(func, node) in _JSON_PARSERS:
|
|
148
|
-
return not any(keyword.arg in {None, "object_hook", "object_pairs_hook", "cls"} for keyword in keywords)
|
|
149
|
-
case ast.Call(func=ast.Attribute(value=receiver, attr="get")) | ast.Subscript(value=receiver):
|
|
150
|
-
return _untyped_receiver(receiver, facts, seen=seen | {node})
|
|
151
|
-
case ast.Call(func=ast.Name(id=name)):
|
|
152
|
-
function = facts.provenance.binding(name, node)
|
|
153
|
-
return (
|
|
154
|
-
isinstance(function, ast.FunctionDef)
|
|
155
|
-
and not function.decorator_list
|
|
156
|
-
and function.returns is not None
|
|
157
|
-
and (
|
|
158
|
-
facts.annotation(function.returns, function.returns, mapping=True)
|
|
159
|
-
or _erased_annotation(function.returns, facts)
|
|
160
|
-
)
|
|
161
|
-
)
|
|
162
|
-
case _:
|
|
163
|
-
return False
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def _name_receiver(node: ast.Name, facts: MappingShapeFacts, seen: frozenset[ast.AST]) -> bool:
|
|
167
|
-
if node.id in facts.provenance.external_bindings:
|
|
168
|
-
return False
|
|
169
|
-
binding = facts.provenance.binding(node.id, node)
|
|
170
|
-
if binding is None or binding in seen:
|
|
171
|
-
return False
|
|
172
|
-
if isinstance(binding, ast.arg):
|
|
173
|
-
return binding.annotation is not None and _erased_annotation(binding.annotation, facts)
|
|
174
|
-
parent = facts.context.parents.get(binding)
|
|
175
|
-
if isinstance(parent, ast.AnnAssign):
|
|
176
|
-
return _erased_annotation(parent.annotation, facts)
|
|
177
|
-
value = facts.provenance.assignment_value(binding, node) if isinstance(binding, ast.Name) else None
|
|
178
|
-
return value is not None and _untyped_receiver(value, facts, seen=seen | {binding})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def _erased_annotation(node: ast.expr, facts: MappingShapeFacts) -> bool:
|
|
182
|
-
resolved = facts.resolve_annotation(node)
|
|
183
|
-
if resolved is None:
|
|
184
|
-
return False
|
|
185
|
-
return (
|
|
186
|
-
facts.builtin(resolved.expression, "dict", resolved.at)
|
|
187
|
-
or facts.builtin(resolved.expression, "object", resolved.at)
|
|
188
|
-
or facts.qualified(resolved.expression, resolved.at) in _UNTYPED
|
|
189
|
-
)
|
|
File without changes
|