sarj-python-lint 0.74.0__tar.gz → 0.75.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/PKG-INFO +1 -1
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/pyproject.toml +1 -1
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_registry.py +2 -0
- sarj_python_lint-0.75.0/src/sarj_python_lint/rules/prefer_one_for_required_row.py +175 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/.gitignore +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/LICENSE +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/README.md +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/created_at_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/defect_xfail_requires_strict.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/duplicated_override_docstring.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/fastapi_openapi_contract.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/get_delegates_to_get_many.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_long_comment.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/preserve_declared_nominal_id.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/preserve_enum_types.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/require_validated_row_factory.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/source_coupled_test.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/sql_requires_injected_pool_owner.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/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.
|
|
3
|
+
Version: 0.75.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -96,6 +96,7 @@ from sarj_python_lint.rules.prefer_nominal_id_types import PreferNominalIdTypes
|
|
|
96
96
|
from sarj_python_lint.rules.prefer_non_nullable_collection import (
|
|
97
97
|
PreferNonNullableCollection,
|
|
98
98
|
)
|
|
99
|
+
from sarj_python_lint.rules.prefer_one_for_required_row import PreferOneForRequiredRow
|
|
99
100
|
from sarj_python_lint.rules.prefer_or_pattern import PreferOrPattern
|
|
100
101
|
from sarj_python_lint.rules.prefer_self_documenting_constant import (
|
|
101
102
|
PreferSelfDocumentingConstant,
|
|
@@ -186,6 +187,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
186
187
|
OpaqueParametrizeCaseNeedsId.id: OpaqueParametrizeCaseNeedsId,
|
|
187
188
|
FixtureReturnsBareTuple.id: FixtureReturnsBareTuple,
|
|
188
189
|
GetDelegatesToGetMany.id: GetDelegatesToGetMany,
|
|
190
|
+
PreferOneForRequiredRow.id: PreferOneForRequiredRow,
|
|
189
191
|
KwargHeavyConstructionInTest.id: KwargHeavyConstructionInTest,
|
|
190
192
|
DefectXfailRequiresStrict.id: DefectXfailRequiresStrict,
|
|
191
193
|
NoFirstPartyPrivateImport.id: NoFirstPartyPrivateImport,
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from itertools import pairwise
|
|
6
|
+
from pathlib import PurePosixPath
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar, final, override
|
|
8
|
+
|
|
9
|
+
from sarj_python_lint.rule_base import (
|
|
10
|
+
AutofixPolicy,
|
|
11
|
+
Diagnostic,
|
|
12
|
+
ExampleFile,
|
|
13
|
+
ExampleOutcome,
|
|
14
|
+
Rule,
|
|
15
|
+
RuleCategory,
|
|
16
|
+
RuleDocumentation,
|
|
17
|
+
RuleExample,
|
|
18
|
+
Severity,
|
|
19
|
+
parse_or_none,
|
|
20
|
+
)
|
|
21
|
+
from sarj_python_lint.rules._paths import is_generated
|
|
22
|
+
from sarj_python_lint.rules._sql import is_store_module
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@final
|
|
30
|
+
class PreferOneForRequiredRow(Rule):
|
|
31
|
+
id = "prefer-one-for-required-row"
|
|
32
|
+
code = "SARJ422"
|
|
33
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
34
|
+
summary="Use a required-row helper instead of asserting that `fetchone()` returned a row.",
|
|
35
|
+
rationale=(
|
|
36
|
+
"A bare assertion is removed by optimized Python and repeats the database contract at each call site; "
|
|
37
|
+
"a shared helper preserves the invariant and raises one intentional domain error."
|
|
38
|
+
),
|
|
39
|
+
remediation=(
|
|
40
|
+
"Wrap the fetch in the repository's required-row helper, for example "
|
|
41
|
+
"`row = one(await cursor.fetchone())`, and remove the non-None assertion."
|
|
42
|
+
),
|
|
43
|
+
category=RuleCategory.CORRECTNESS,
|
|
44
|
+
autofix=AutofixPolicy.NONE,
|
|
45
|
+
limitations=(
|
|
46
|
+
"Only an immediate exact `row is not None` assertion after `fetchone()` in production store modules is inspected.",
|
|
47
|
+
"The rule does not infer that an optional lookup must return a row and does not choose an import path for the helper.",
|
|
48
|
+
),
|
|
49
|
+
examples=(
|
|
50
|
+
RuleExample(
|
|
51
|
+
example_id="required-returning-row",
|
|
52
|
+
title="Use the shared required-row contract",
|
|
53
|
+
outcome=ExampleOutcome.MATCH,
|
|
54
|
+
files=(
|
|
55
|
+
ExampleFile.python(
|
|
56
|
+
"app/settings_store.py",
|
|
57
|
+
"async def save(cursor):\n"
|
|
58
|
+
" row = await cursor.fetchone()\n"
|
|
59
|
+
" assert row is not None, 'RETURNING must yield a row'\n"
|
|
60
|
+
" return row\n",
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
focus_path=PurePosixPath("app/settings_store.py"),
|
|
64
|
+
expected_count=1,
|
|
65
|
+
public=True,
|
|
66
|
+
),
|
|
67
|
+
RuleExample(
|
|
68
|
+
example_id="required-row-helper",
|
|
69
|
+
title="Centralize the missing-row failure",
|
|
70
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
71
|
+
files=(
|
|
72
|
+
ExampleFile.python(
|
|
73
|
+
"app/settings_store.py",
|
|
74
|
+
"async def save(cursor):\n return one(await cursor.fetchone())\n",
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
focus_path=PurePosixPath("app/settings_store.py"),
|
|
78
|
+
expected_count=0,
|
|
79
|
+
public=True,
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
description = documentation.summary
|
|
84
|
+
|
|
85
|
+
@override
|
|
86
|
+
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
87
|
+
if not is_store_module(path) or is_generated(path, source):
|
|
88
|
+
return []
|
|
89
|
+
tree = parse_or_none(path, source)
|
|
90
|
+
if tree is None:
|
|
91
|
+
return []
|
|
92
|
+
diagnostics: list[Diagnostic] = []
|
|
93
|
+
for owner in ast.walk(tree):
|
|
94
|
+
for statements in _statement_blocks(owner):
|
|
95
|
+
for assignment, assertion in pairwise(statements):
|
|
96
|
+
binding = _fetchone_binding(assignment)
|
|
97
|
+
if binding is None or not _asserts_not_none(assertion, binding.name):
|
|
98
|
+
continue
|
|
99
|
+
diagnostics.append(
|
|
100
|
+
Diagnostic(
|
|
101
|
+
path=path,
|
|
102
|
+
line=assignment.lineno,
|
|
103
|
+
col=assignment.col_offset + 1,
|
|
104
|
+
code=self.code,
|
|
105
|
+
message=(
|
|
106
|
+
f"`{binding.name}` is asserted non-None immediately after `fetchone()`. Use the shared "
|
|
107
|
+
f"required-row helper (for example `{binding.name} = one({binding.expression})`) so "
|
|
108
|
+
"optimized Python cannot remove the contract."
|
|
109
|
+
),
|
|
110
|
+
severity=Severity.WARNING,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
return diagnostics
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _statement_blocks(node: ast.AST) -> tuple[list[ast.stmt], ...]:
|
|
117
|
+
match node:
|
|
118
|
+
case ast.Module() | ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
|
|
119
|
+
return (node.body,)
|
|
120
|
+
case ast.If() | ast.For() | ast.AsyncFor() | ast.While():
|
|
121
|
+
return node.body, node.orelse
|
|
122
|
+
case ast.With() | ast.AsyncWith() | ast.ExceptHandler() | ast.match_case():
|
|
123
|
+
return (node.body,)
|
|
124
|
+
case ast.Try() | ast.TryStar():
|
|
125
|
+
return node.body, node.orelse, node.finalbody, *(handler.body for handler in node.handlers)
|
|
126
|
+
case _:
|
|
127
|
+
return ()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True, slots=True)
|
|
131
|
+
class _FetchBinding:
|
|
132
|
+
name: str
|
|
133
|
+
expression: str
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _fetchone_binding(statement: ast.stmt) -> _FetchBinding | None:
|
|
137
|
+
target: ast.expr
|
|
138
|
+
value: ast.expr | None
|
|
139
|
+
if isinstance(statement, ast.Assign) and len(statement.targets) == 1:
|
|
140
|
+
target = statement.targets[0]
|
|
141
|
+
value = statement.value
|
|
142
|
+
elif isinstance(statement, ast.AnnAssign) and statement.value is not None:
|
|
143
|
+
target = statement.target
|
|
144
|
+
value = statement.value
|
|
145
|
+
else:
|
|
146
|
+
return None
|
|
147
|
+
if not isinstance(target, ast.Name):
|
|
148
|
+
return None
|
|
149
|
+
call = value.value if isinstance(value, ast.Await) else value
|
|
150
|
+
if not isinstance(call, ast.Call) or call.args or call.keywords:
|
|
151
|
+
return None
|
|
152
|
+
if not isinstance(call.func, ast.Attribute) or call.func.attr != "fetchone":
|
|
153
|
+
return None
|
|
154
|
+
return _FetchBinding(target.id, ast.unparse(value))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _asserts_not_none(statement: ast.stmt, binding: str) -> bool:
|
|
158
|
+
if not isinstance(statement, ast.Assert) or not isinstance(statement.test, ast.Compare):
|
|
159
|
+
return False
|
|
160
|
+
comparison = statement.test
|
|
161
|
+
if len(comparison.ops) != 1 or not isinstance(comparison.ops[0], ast.IsNot) or len(comparison.comparators) != 1:
|
|
162
|
+
return False
|
|
163
|
+
left, right = comparison.left, comparison.comparators[0]
|
|
164
|
+
return (_is_name(left, binding) and _is_none(right)) or (_is_none(left) and _is_name(right, binding))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _is_name(node: ast.expr, expected: str) -> bool:
|
|
168
|
+
return isinstance(node, ast.Name) and node.id == expected
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _is_none(node: ast.expr) -> bool:
|
|
172
|
+
return isinstance(node, ast.Constant) and node.value is None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
__all__ = ["PreferOneForRequiredRow"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_long_comment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_select_star.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/no_stdlib_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_class_row.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/prefer_str_enum.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.74.0 → sarj_python_lint-0.75.0}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|