sarj-python-lint 0.80.4__tar.gz → 0.80.6__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-0.80.4 → sarj_python_lint-0.80.6}/PKG-INFO +2 -1
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/pyproject.toml +2 -2
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/__main__.py +3 -5
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_fastapi.py +12 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_imports.py +6 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_prose_budget.py +121 -11
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_registry.py +12 -14
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_sql.py +9 -4
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +505 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +144 -54
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/docstring_args_restate_signature.py +215 -0
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +326 -0
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/excessive_commentary.py +218 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/fakes_in_shared_location.py +64 -13
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +99 -21
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/iac_source_coupled_test.py +14 -6
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +219 -14
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/mock_without_spec.py +255 -35
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +103 -42
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +187 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +33 -21
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +270 -0
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_offset_pagination.py +167 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_redundant_literal_description.py +124 -49
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_repeated_string_literal.py +83 -41
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_repeated_test_body.py +41 -50
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_restated_comment.py +56 -18
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_secret_in_log.py +183 -0
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_select_star.py +258 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +206 -193
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_tautological_expect.py +218 -0
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_typed_doc_sections.py +134 -0
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_unique_violation_message_match.py +399 -0
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +202 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +181 -54
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +273 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/source_coupled_test.py +27 -11
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +103 -22
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +68 -15
- sarj_python_lint-0.80.6/src/sarj_python_lint/rules/typed_error_reasons.py +228 -0
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -140
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -201
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -212
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -299
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_long_comment.py +0 -114
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -171
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_offset_pagination.py +0 -118
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -202
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -155
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_secret_in_log.py +0 -171
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_select_star.py +0 -138
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -580
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -167
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_tautological_expect.py +0 -279
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -86
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -170
- sarj_python_lint-0.80.4/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -206
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/.gitignore +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/LICENSE +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/README.md +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_one_for_required_row.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/preserve_declared_nominal_id.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/preserve_enum_types.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/require_validated_row_factory.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/sql_requires_injected_pool_owner.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 0.80.
|
|
3
|
+
Version: 0.80.6
|
|
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/
|
|
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
18
18
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Requires-Dist: ruff>=0.16.5
|
|
21
|
+
Requires-Dist: sqlglot==30.18.0
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
|
|
23
24
|
<!-- Generated by `code-standards maintain docs sync`; do not edit. -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "0.80.
|
|
3
|
+
version = "0.80.6"
|
|
4
4
|
description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "sarj-ai" }]
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
"Topic :: Software Development :: Quality Assurance",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["ruff>=0.16.5"]
|
|
18
|
+
dependencies = ["ruff>=0.16.5", "sqlglot==30.18.0"]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
sarj-python-lint = "sarj_python_lint.__main__:main"
|
|
@@ -125,14 +125,12 @@ def analyze(
|
|
|
125
125
|
|
|
126
126
|
_DIAGNOSTIC_PRECEDENCE = MappingProxyType(
|
|
127
127
|
{
|
|
128
|
-
"SARJ003": frozenset({"SARJ080"}),
|
|
129
128
|
"SARJ050": frozenset({"SARJ420"}),
|
|
130
|
-
"SARJ084": frozenset({"SARJ050", "
|
|
129
|
+
"SARJ084": frozenset({"SARJ050", "SARJ420"}),
|
|
131
130
|
"SARJ085": frozenset({"SARJ420"}),
|
|
132
131
|
"SARJ086": frozenset({"SARJ420"}),
|
|
133
132
|
"SARJ087": frozenset({"SARJ420"}),
|
|
134
|
-
"SARJ088": frozenset({"SARJ050", "SARJ085", "
|
|
135
|
-
"SARJ091": frozenset({"SARJ420"}),
|
|
133
|
+
"SARJ088": frozenset({"SARJ050", "SARJ085", "SARJ420"}),
|
|
136
134
|
"SARJ092": frozenset({"SARJ086", "SARJ087", "SARJ420"}),
|
|
137
135
|
"SARJ093": frozenset({"SARJ034"}),
|
|
138
136
|
"SARJ099": frozenset({"SARJ420"}),
|
|
@@ -140,7 +138,7 @@ _DIAGNOSTIC_PRECEDENCE = MappingProxyType(
|
|
|
140
138
|
)
|
|
141
139
|
|
|
142
140
|
_DOCSTRING_PRECEDENCE_CODES = frozenset(
|
|
143
|
-
{"SARJ050", "SARJ084", "SARJ085", "SARJ086", "SARJ087", "SARJ088", "
|
|
141
|
+
{"SARJ050", "SARJ084", "SARJ085", "SARJ086", "SARJ087", "SARJ088", "SARJ092", "SARJ099"}
|
|
144
142
|
)
|
|
145
143
|
|
|
146
144
|
|
|
@@ -41,6 +41,8 @@ class Route:
|
|
|
41
41
|
path: str | None
|
|
42
42
|
receiver_kind: Literal["FastAPI", "APIRouter"]
|
|
43
43
|
inherited_hidden: bool = False
|
|
44
|
+
inherited_responses: ast.expr | None = None
|
|
45
|
+
inherited_response_class: ast.expr | None = None
|
|
44
46
|
|
|
45
47
|
@property
|
|
46
48
|
def keywords(self) -> dict[str, ast.expr]:
|
|
@@ -119,6 +121,7 @@ class FastapiIndex:
|
|
|
119
121
|
self.receiver_origins: dict[_ReceiverKey, _ReceiverKey] = {}
|
|
120
122
|
self.receiver_kinds: dict[_ReceiverKey, Literal["FastAPI", "APIRouter"]] = {}
|
|
121
123
|
self.hidden_receivers: set[_ReceiverKey] = set()
|
|
124
|
+
self.receiver_defaults: dict[_ReceiverKey, dict[str, ast.expr]] = {}
|
|
122
125
|
self.decorators: dict[_ReceiverKey, _DecoratorBinding] = {}
|
|
123
126
|
self.bound_names: set[_ReceiverKey] = set()
|
|
124
127
|
self._node_scopes: dict[int, int] = {}
|
|
@@ -243,6 +246,12 @@ class FastapiIndex:
|
|
|
243
246
|
self.receivers.add(key)
|
|
244
247
|
self.receiver_origins[key] = key
|
|
245
248
|
self.receiver_kinds[key] = constructor_kind
|
|
249
|
+
if isinstance(value, ast.Call):
|
|
250
|
+
self.receiver_defaults[key] = {
|
|
251
|
+
keyword.arg: keyword.value
|
|
252
|
+
for keyword in value.keywords
|
|
253
|
+
if keyword.arg in {"responses", "default_response_class"}
|
|
254
|
+
}
|
|
246
255
|
if self._constructor_is_hidden(value):
|
|
247
256
|
self.hidden_receivers.add(key)
|
|
248
257
|
changed = True
|
|
@@ -346,6 +355,7 @@ class FastapiIndex:
|
|
|
346
355
|
path = path_node.value if isinstance(path_node, ast.Constant) and isinstance(path_node.value, str) else None
|
|
347
356
|
route_methods = self._route_methods(decorator) if method == "api_route" else (method,)
|
|
348
357
|
origin = self.receiver_origins[receiver_key]
|
|
358
|
+
defaults = self.receiver_defaults.get(origin, {})
|
|
349
359
|
routes.extend(
|
|
350
360
|
Route(
|
|
351
361
|
decorator=decorator,
|
|
@@ -354,6 +364,8 @@ class FastapiIndex:
|
|
|
354
364
|
path=path,
|
|
355
365
|
receiver_kind=self.receiver_kinds[receiver_key],
|
|
356
366
|
inherited_hidden=receiver_key in self.hidden_receivers,
|
|
367
|
+
inherited_responses=defaults.get("responses"),
|
|
368
|
+
inherited_response_class=defaults.get("default_response_class"),
|
|
357
369
|
)
|
|
358
370
|
for route_method in route_methods
|
|
359
371
|
)
|
|
@@ -49,6 +49,12 @@ class ImportIndex:
|
|
|
49
49
|
resolved = self._resolve(node)
|
|
50
50
|
return resolved is not None and resolved.module in sources and resolved.symbol == symbol
|
|
51
51
|
|
|
52
|
+
def resolved_symbol(self, node: ast.expr, *, sources: frozenset[str]) -> str | None:
|
|
53
|
+
resolved = self._resolve(node)
|
|
54
|
+
if resolved is None or resolved.module not in sources:
|
|
55
|
+
return None
|
|
56
|
+
return resolved.symbol
|
|
57
|
+
|
|
52
58
|
def builtin_is_unshadowed(self, name: str) -> bool:
|
|
53
59
|
return name not in self.shadowed_names and name not in self.bindings
|
|
54
60
|
|
{sarj_python_lint-0.80.4 → sarj_python_lint-0.80.6}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
@@ -198,7 +198,7 @@ def _docstring_groups(tree: ast.Module) -> list[ProseGroup]:
|
|
|
198
198
|
):
|
|
199
199
|
continue
|
|
200
200
|
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)) and (
|
|
201
|
-
|
|
201
|
+
_decorator_terminal_names(node) & PROMPT_DECORATOR_MARKERS
|
|
202
202
|
):
|
|
203
203
|
continue
|
|
204
204
|
if isinstance(node, ast.ClassDef) and _is_schema_class(node):
|
|
@@ -244,6 +244,17 @@ def _is_schema_class(node: ast.ClassDef) -> bool:
|
|
|
244
244
|
return bool(bases & _SCHEMA_BASES or decorator_markers(node) & _SCHEMA_DECORATORS)
|
|
245
245
|
|
|
246
246
|
|
|
247
|
+
def _decorator_terminal_names(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> frozenset[str]:
|
|
248
|
+
names: set[str] = set()
|
|
249
|
+
for decorator in node.decorator_list:
|
|
250
|
+
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
251
|
+
if isinstance(target, ast.Name):
|
|
252
|
+
names.add(target.id.casefold())
|
|
253
|
+
elif isinstance(target, ast.Attribute):
|
|
254
|
+
names.add(target.attr.casefold())
|
|
255
|
+
return frozenset(names)
|
|
256
|
+
|
|
257
|
+
|
|
247
258
|
def _fully_typed(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
248
259
|
args = node.args
|
|
249
260
|
parameters = (*args.posonlyargs, *args.args, *args.kwonlyargs)
|
|
@@ -270,21 +281,23 @@ def _typed_restatement_lines(
|
|
|
270
281
|
docstring_line: int,
|
|
271
282
|
) -> tuple[int, ...]:
|
|
272
283
|
annotations = {
|
|
273
|
-
arg.arg:
|
|
284
|
+
arg.arg: _annotation_type(arg.annotation)
|
|
274
285
|
for arg in (*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs)
|
|
275
286
|
if arg.annotation is not None
|
|
276
287
|
}
|
|
277
288
|
if node.args.vararg is not None and node.args.vararg.annotation is not None:
|
|
278
|
-
annotations[f"*{node.args.vararg.arg}"] =
|
|
289
|
+
annotations[f"*{node.args.vararg.arg}"] = _annotation_type(node.args.vararg.annotation)
|
|
279
290
|
if node.args.kwarg is not None and node.args.kwarg.annotation is not None:
|
|
280
|
-
annotations[f"**{node.args.kwarg.arg}"] =
|
|
281
|
-
return_type =
|
|
282
|
-
|
|
291
|
+
annotations[f"**{node.args.kwarg.arg}"] = _annotation_type(node.args.kwarg.annotation)
|
|
292
|
+
return_type = _annotation_type(node.returns) if node.returns is not None else None
|
|
293
|
+
yield_type = _yielded_type(node.returns) if node.returns is not None else None
|
|
294
|
+
visible_doc = _without_literal_examples(doc)
|
|
295
|
+
body_line_counts = _section_body_line_counts(visible_doc)
|
|
283
296
|
|
|
284
297
|
active: str | None = None
|
|
285
298
|
active_header: int | None = None
|
|
286
299
|
result: list[int] = []
|
|
287
|
-
for index, raw in enumerate(
|
|
300
|
+
for index, raw in enumerate(visible_doc.splitlines()):
|
|
288
301
|
if heading := _SECTION_HEADING_RE.match(raw):
|
|
289
302
|
active = heading.group(1)
|
|
290
303
|
active_header = index
|
|
@@ -297,20 +310,63 @@ def _typed_restatement_lines(
|
|
|
297
310
|
continue
|
|
298
311
|
if active in {"Args", "Arguments", "Parameters", "Params", "Keyword Args", "Keyword Arguments"}:
|
|
299
312
|
match = _ARG_TYPE_ENTRY_RE.match(raw) or _NUMPY_ARG_TYPE_ENTRY_RE.match(raw)
|
|
300
|
-
if match is not None and
|
|
313
|
+
if match is not None and _documented_type(match.group(2)) == annotations.get(match.group(1)):
|
|
301
314
|
result.append(docstring_line + index)
|
|
302
315
|
elif match := _RETURN_TYPE_ENTRY_RE.match(raw):
|
|
303
|
-
if
|
|
316
|
+
expected = yield_type if active in {"Yields", "Yield"} else return_type
|
|
317
|
+
if expected is not None and _documented_type(match.group(1)) == expected:
|
|
304
318
|
result.append(docstring_line + index)
|
|
305
319
|
elif (
|
|
306
320
|
active_header is not None
|
|
307
321
|
and body_line_counts.get(active_header) == 1
|
|
308
|
-
and
|
|
322
|
+
and (expected := yield_type if active in {"Yields", "Yield"} else return_type) is not None
|
|
323
|
+
and _documented_type(raw.strip()) == expected
|
|
309
324
|
):
|
|
310
325
|
result.append(docstring_line + index)
|
|
311
326
|
return tuple(result)
|
|
312
327
|
|
|
313
328
|
|
|
329
|
+
def _without_literal_examples(doc: str) -> str:
|
|
330
|
+
visible: list[str] = []
|
|
331
|
+
fence: str | None = None
|
|
332
|
+
literal_indent: int | None = None
|
|
333
|
+
awaiting_literal_block = False
|
|
334
|
+
for raw in doc.splitlines():
|
|
335
|
+
stripped = raw.lstrip()
|
|
336
|
+
indent = len(raw) - len(stripped)
|
|
337
|
+
fence_marker = stripped[:3] if stripped.startswith(("```", "~~~")) else None
|
|
338
|
+
if fence is not None:
|
|
339
|
+
visible.append("")
|
|
340
|
+
if fence_marker == fence:
|
|
341
|
+
fence = None
|
|
342
|
+
continue
|
|
343
|
+
if fence_marker is not None:
|
|
344
|
+
fence = fence_marker
|
|
345
|
+
visible.append("")
|
|
346
|
+
continue
|
|
347
|
+
if stripped.startswith((">>>", "...")):
|
|
348
|
+
visible.append("")
|
|
349
|
+
continue
|
|
350
|
+
if literal_indent is not None:
|
|
351
|
+
if not stripped or indent > literal_indent:
|
|
352
|
+
visible.append("")
|
|
353
|
+
continue
|
|
354
|
+
literal_indent = None
|
|
355
|
+
if awaiting_literal_block:
|
|
356
|
+
if not stripped:
|
|
357
|
+
visible.append(raw)
|
|
358
|
+
continue
|
|
359
|
+
if indent > 0:
|
|
360
|
+
literal_indent = indent - 1
|
|
361
|
+
visible.append("")
|
|
362
|
+
awaiting_literal_block = False
|
|
363
|
+
continue
|
|
364
|
+
awaiting_literal_block = False
|
|
365
|
+
visible.append(raw)
|
|
366
|
+
awaiting_literal_block = stripped.endswith("::")
|
|
367
|
+
return "\n".join(visible)
|
|
368
|
+
|
|
369
|
+
|
|
314
370
|
def _section_body_line_counts(doc: str) -> dict[int, int]:
|
|
315
371
|
counts: dict[int, int] = {}
|
|
316
372
|
active_header: int | None = None
|
|
@@ -325,5 +381,59 @@ def _section_body_line_counts(doc: str) -> dict[int, int]:
|
|
|
325
381
|
return counts
|
|
326
382
|
|
|
327
383
|
|
|
384
|
+
def _annotation_type(node: ast.expr) -> str:
|
|
385
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
386
|
+
parsed = _parse_type_expression(node.value)
|
|
387
|
+
return _normalise_type(ast.unparse(parsed)) if parsed is not None else node.value
|
|
388
|
+
return _normalise_type(ast.unparse(node))
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _documented_type(value: str) -> str | None:
|
|
392
|
+
parsed = _parse_type_expression(value)
|
|
393
|
+
return None if parsed is None else _normalise_type(ast.unparse(parsed))
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _parse_type_expression(value: str) -> ast.expr | None:
|
|
397
|
+
try:
|
|
398
|
+
parsed = ast.parse(value.strip(), mode="eval").body
|
|
399
|
+
except SyntaxError:
|
|
400
|
+
return None
|
|
401
|
+
unsafe = (
|
|
402
|
+
ast.Await,
|
|
403
|
+
ast.BoolOp,
|
|
404
|
+
ast.Call,
|
|
405
|
+
ast.Compare,
|
|
406
|
+
ast.DictComp,
|
|
407
|
+
ast.GeneratorExp,
|
|
408
|
+
ast.IfExp,
|
|
409
|
+
ast.Lambda,
|
|
410
|
+
ast.ListComp,
|
|
411
|
+
ast.NamedExpr,
|
|
412
|
+
ast.SetComp,
|
|
413
|
+
ast.Yield,
|
|
414
|
+
ast.YieldFrom,
|
|
415
|
+
)
|
|
416
|
+
if any(isinstance(part, unsafe) for part in ast.walk(parsed)):
|
|
417
|
+
return None
|
|
418
|
+
if any(isinstance(part, ast.BinOp) and not isinstance(part.op, ast.BitOr) for part in ast.walk(parsed)):
|
|
419
|
+
return None
|
|
420
|
+
return parsed
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _yielded_type(node: ast.expr) -> str | None:
|
|
424
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
425
|
+
parsed = _parse_type_expression(node.value)
|
|
426
|
+
if parsed is None:
|
|
427
|
+
return None
|
|
428
|
+
node = parsed
|
|
429
|
+
if not isinstance(node, ast.Subscript):
|
|
430
|
+
return None
|
|
431
|
+
name = node.value.attr if isinstance(node.value, ast.Attribute) else node.value.id if isinstance(node.value, ast.Name) else ""
|
|
432
|
+
if name not in {"AsyncGenerator", "AsyncIterable", "AsyncIterator", "Generator", "Iterable", "Iterator"}:
|
|
433
|
+
return None
|
|
434
|
+
members = node.slice.elts if isinstance(node.slice, ast.Tuple) else (node.slice,)
|
|
435
|
+
return _annotation_type(members[0]) if members else None
|
|
436
|
+
|
|
437
|
+
|
|
328
438
|
def _normalise_type(value: str) -> str:
|
|
329
|
-
return re.sub(r"\s+", "", value).removeprefix("typing.").
|
|
439
|
+
return re.sub(r"\s+", "", value).removeprefix("typing.").casefold()
|
|
@@ -3,6 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
from types import MappingProxyType
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
from sarj_python_lint.rules.complex_postgres_query_requires_architecture_review import (
|
|
7
|
+
ComplexPostgresQueryRequiresArchitectureReview,
|
|
8
|
+
)
|
|
6
9
|
from sarj_python_lint.rules.defect_xfail_requires_explicit_strict import DefectXfailRequiresExplicitStrict
|
|
7
10
|
from sarj_python_lint.rules.docstring_args_restate_signature import (
|
|
8
11
|
DocstringArgsRestateSignature,
|
|
@@ -10,6 +13,7 @@ from sarj_python_lint.rules.docstring_args_restate_signature import (
|
|
|
10
13
|
from sarj_python_lint.rules.docstring_returns_restate_signature import (
|
|
11
14
|
DocstringReturnsRestateSignature,
|
|
12
15
|
)
|
|
16
|
+
from sarj_python_lint.rules.excessive_commentary import ExcessiveCommentary
|
|
13
17
|
from sarj_python_lint.rules.fakes_in_shared_location import FakesInSharedLocation
|
|
14
18
|
from sarj_python_lint.rules.fastapi_explicit_openapi_contract import FastapiExplicitOpenapiContract
|
|
15
19
|
from sarj_python_lint.rules.iac_source_coupled_test import IacSourceCoupledTest
|
|
@@ -44,14 +48,8 @@ from sarj_python_lint.rules.no_generic_single_export_module import NoGenericSing
|
|
|
44
48
|
from sarj_python_lint.rules.no_hidden_constructor_fallback import (
|
|
45
49
|
NoHiddenConstructorFallback,
|
|
46
50
|
)
|
|
47
|
-
from sarj_python_lint.rules.no_isinstance_union_chain import NoIsinstanceUnionChain
|
|
48
|
-
from sarj_python_lint.rules.no_long_comment import NoLongComment
|
|
49
51
|
from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
|
|
50
52
|
from sarj_python_lint.rules.no_offset_pagination import NoOffsetPagination
|
|
51
|
-
from sarj_python_lint.rules.no_optional_tenant_predicate import (
|
|
52
|
-
NoOptionalTenantPredicate,
|
|
53
|
-
)
|
|
54
|
-
from sarj_python_lint.rules.no_query_with_many_joins import NoQueryWithManyJoins
|
|
55
53
|
from sarj_python_lint.rules.no_random_uuid_in_sql import NoRandomUuidInSql
|
|
56
54
|
from sarj_python_lint.rules.no_raw_connection_in_tests import NoRawConnectionInTests
|
|
57
55
|
from sarj_python_lint.rules.no_redundant_literal_description import NoRedundantLiteralDescription
|
|
@@ -60,8 +58,6 @@ from sarj_python_lint.rules.no_repeated_test_body import NoRepeatedTestBody
|
|
|
60
58
|
from sarj_python_lint.rules.no_restated_comment import NoRestatedComment
|
|
61
59
|
from sarj_python_lint.rules.no_secret_in_log import NoSecretInLog
|
|
62
60
|
from sarj_python_lint.rules.no_select_star import NoSelectStar
|
|
63
|
-
from sarj_python_lint.rules.no_sentinel_return_on_except import NoSentinelReturnOnExcept
|
|
64
|
-
from sarj_python_lint.rules.no_stdlib_logging import NoStdlibLogging
|
|
65
61
|
from sarj_python_lint.rules.no_string_concat_in_loop import NoStringConcatInLoop
|
|
66
62
|
from sarj_python_lint.rules.no_tautological_expect import NoTautologicalExpect
|
|
67
63
|
from sarj_python_lint.rules.no_typed_doc_sections import NoTypedDocSections
|
|
@@ -72,6 +68,9 @@ from sarj_python_lint.rules.no_unnecessary_docstring import NoUnnecessaryDocstri
|
|
|
72
68
|
from sarj_python_lint.rules.no_vague_suppression_description import (
|
|
73
69
|
NoVagueSuppressionDescription,
|
|
74
70
|
)
|
|
71
|
+
from sarj_python_lint.rules.no_whole_request_response_payload_in_log import (
|
|
72
|
+
NoWholeRequestResponsePayloadInLog,
|
|
73
|
+
)
|
|
75
74
|
from sarj_python_lint.rules.opaque_parametrize_case_needs_id import OpaqueParametrizeCaseNeedsId
|
|
76
75
|
from sarj_python_lint.rules.over_mocked_test import OverMockedTest
|
|
77
76
|
from sarj_python_lint.rules.phase_label_comment import TestPhaseLabelComment
|
|
@@ -152,6 +151,7 @@ from sarj_python_lint.rules.store_insert_requires_on_conflict import (
|
|
|
152
151
|
from sarj_python_lint.rules.timestamp_order_requires_tiebreaker import TimestampOrderRequiresTiebreaker
|
|
153
152
|
from sarj_python_lint.rules.trailing_value_narration import TrailingValueNarration
|
|
154
153
|
from sarj_python_lint.rules.trivially_true_assertion import TriviallyTrueAssertion
|
|
154
|
+
from sarj_python_lint.rules.typed_error_reasons import TypedErrorReasons
|
|
155
155
|
from sarj_python_lint.rules.uncontrolled_randomness_in_test import UncontrolledRandomnessInTest
|
|
156
156
|
from sarj_python_lint.rules.unused_mock_setup import UnusedMockSetup
|
|
157
157
|
|
|
@@ -163,20 +163,20 @@ if TYPE_CHECKING:
|
|
|
163
163
|
|
|
164
164
|
REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
165
165
|
{
|
|
166
|
+
ComplexPostgresQueryRequiresArchitectureReview.id: ComplexPostgresQueryRequiresArchitectureReview,
|
|
167
|
+
ExcessiveCommentary.id: ExcessiveCommentary,
|
|
166
168
|
PreferWalrusAwaitedNoneGuard.id: PreferWalrusAwaitedNoneGuard,
|
|
167
169
|
TimestampOrderRequiresTiebreaker.id: TimestampOrderRequiresTiebreaker,
|
|
168
170
|
NoStringConcatInLoop.id: NoStringConcatInLoop,
|
|
169
171
|
PreferClassRow.id: PreferClassRow,
|
|
170
172
|
PreferCollectionComprehension.id: PreferCollectionComprehension,
|
|
171
173
|
PreferStrEnum.id: PreferStrEnum,
|
|
172
|
-
NoIsinstanceUnionChain.id: NoIsinstanceUnionChain,
|
|
173
174
|
NoOffsetPagination.id: NoOffsetPagination,
|
|
174
175
|
PreferNamedtupleOverTupleReturn.id: PreferNamedtupleOverTupleReturn,
|
|
175
176
|
NoCorsWildcardWithCredentials.id: NoCorsWildcardWithCredentials,
|
|
176
177
|
PydanticAtBoundaries.id: PydanticAtBoundaries,
|
|
177
178
|
FastapiExplicitOpenapiContract.id: FastapiExplicitOpenapiContract,
|
|
178
179
|
FakesInSharedLocation.id: FakesInSharedLocation,
|
|
179
|
-
NoSentinelReturnOnExcept.id: NoSentinelReturnOnExcept,
|
|
180
180
|
PreferConstantTimeSecretCompare.id: PreferConstantTimeSecretCompare,
|
|
181
181
|
NoSecretInLog.id: NoSecretInLog,
|
|
182
182
|
PreferTimedeltaForDurations.id: PreferTimedeltaForDurations,
|
|
@@ -186,7 +186,6 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
186
186
|
StoreInsertRequiresOnConflict.id: StoreInsertRequiresOnConflict,
|
|
187
187
|
SourceCoupledTest.id: SourceCoupledTest,
|
|
188
188
|
IacSourceCoupledTest.id: IacSourceCoupledTest,
|
|
189
|
-
NoQueryWithManyJoins.id: NoQueryWithManyJoins,
|
|
190
189
|
NoRawConnectionInTests.id: NoRawConnectionInTests,
|
|
191
190
|
NoAnalyticalAggregationInPostgresStore.id: NoAnalyticalAggregationInPostgresStore,
|
|
192
191
|
NoSelectStar.id: NoSelectStar,
|
|
@@ -209,12 +208,10 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
209
208
|
NoRestatedComment.id: NoRestatedComment,
|
|
210
209
|
RedundantDocstring.id: RedundantDocstring,
|
|
211
210
|
TrailingValueNarration.id: TrailingValueNarration,
|
|
212
|
-
NoStdlibLogging.id: NoStdlibLogging,
|
|
213
211
|
NoRandomUuidInSql.id: NoRandomUuidInSql,
|
|
214
212
|
NoHiddenConstructorFallback.id: NoHiddenConstructorFallback,
|
|
215
213
|
NoFileLevelEscapeHatchSuppression.id: NoFileLevelEscapeHatchSuppression,
|
|
216
214
|
NoFastapiOnEvent.id: NoFastapiOnEvent,
|
|
217
|
-
NoOptionalTenantPredicate.id: NoOptionalTenantPredicate,
|
|
218
215
|
NoTautologicalExpect.id: NoTautologicalExpect,
|
|
219
216
|
PreferLibraryFake.id: PreferLibraryFake,
|
|
220
217
|
OverMockedTest.id: OverMockedTest,
|
|
@@ -242,7 +239,6 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
242
239
|
DocstringReturnsRestateSignature.id: DocstringReturnsRestateSignature,
|
|
243
240
|
RestatedTestDocstring.id: RestatedTestDocstring,
|
|
244
241
|
TestPhaseLabelComment.id: TestPhaseLabelComment,
|
|
245
|
-
NoLongComment.id: NoLongComment,
|
|
246
242
|
NoNestedPydanticFieldValidator.id: NoNestedPydanticFieldValidator,
|
|
247
243
|
NoRedundantLiteralDescription.id: NoRedundantLiteralDescription,
|
|
248
244
|
NoTypedDocSections.id: NoTypedDocSections,
|
|
@@ -260,6 +256,8 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
260
256
|
RequirePydanticOrdinalLowerBound.id: RequirePydanticOrdinalLowerBound,
|
|
261
257
|
RequireNoDecodeForSplittingSettingsField.id: RequireNoDecodeForSplittingSettingsField,
|
|
262
258
|
NoVagueSuppressionDescription.id: NoVagueSuppressionDescription,
|
|
259
|
+
NoWholeRequestResponsePayloadInLog.id: NoWholeRequestResponsePayloadInLog,
|
|
260
|
+
TypedErrorReasons.id: TypedErrorReasons,
|
|
263
261
|
}
|
|
264
262
|
)
|
|
265
263
|
|
|
@@ -16,12 +16,12 @@ def is_store_module(path: Path) -> bool:
|
|
|
16
16
|
return path.name == "store.py" or path.name.endswith("_store.py") or "stores" in path.parts
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def sql_string_value(node: ast.expr) -> str | None:
|
|
19
|
+
def sql_string_value(node: ast.expr, *, interpolation_placeholder: str = " ") -> str | None:
|
|
20
20
|
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
21
21
|
return node.value
|
|
22
22
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
23
|
-
left = sql_string_value(node.left)
|
|
24
|
-
right = sql_string_value(node.right)
|
|
23
|
+
left = sql_string_value(node.left, interpolation_placeholder=interpolation_placeholder)
|
|
24
|
+
right = sql_string_value(node.right, interpolation_placeholder=interpolation_placeholder)
|
|
25
25
|
if left is not None and right is not None:
|
|
26
26
|
return left + right
|
|
27
27
|
if isinstance(node, ast.JoinedStr):
|
|
@@ -32,7 +32,7 @@ def sql_string_value(node: ast.expr) -> str | None:
|
|
|
32
32
|
elif isinstance(value, ast.FormattedValue):
|
|
33
33
|
# Keep static SQL tokens on either side adjacent while ensuring
|
|
34
34
|
# an interpolation can never manufacture SELECT/FROM/* itself.
|
|
35
|
-
pieces.append(
|
|
35
|
+
pieces.append(interpolation_placeholder)
|
|
36
36
|
else: # pragma: no cover - the parser emits only these two forms
|
|
37
37
|
return None
|
|
38
38
|
return "".join(pieces)
|
|
@@ -67,6 +67,11 @@ def strip_sql_noise(text: str, *, mask_dollar_quotes: bool = True) -> str:
|
|
|
67
67
|
out[i] = " "
|
|
68
68
|
i += 1
|
|
69
69
|
continue
|
|
70
|
+
if ch == "#" and (i == 0 or text[i - 1].isspace()):
|
|
71
|
+
while i < n and text[i] != "\n":
|
|
72
|
+
out[i] = " "
|
|
73
|
+
i += 1
|
|
74
|
+
continue
|
|
70
75
|
if ch == "/" and i + 1 < n and text[i + 1] == "*":
|
|
71
76
|
out[i] = out[i + 1] = " "
|
|
72
77
|
i += 2
|