sarj-python-lint 0.73.2__tar.gz → 0.74.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.73.2 → sarj_python_lint-0.74.0}/PKG-INFO +1 -1
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/pyproject.toml +1 -1
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_registry.py +2 -2
- sarj_python_lint-0.74.0/src/sarj_python_lint/rules/get_delegates_to_get_many.py +282 -0
- sarj_python_lint-0.73.2/src/sarj_python_lint/rules/no_file_level_suppression.py +0 -121
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/.gitignore +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/LICENSE +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/README.md +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/created_at_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/defect_xfail_requires_strict.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/duplicated_override_docstring.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/fastapi_openapi_contract.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_long_comment.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/preserve_declared_nominal_id.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/preserve_enum_types.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/require_validated_row_factory.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/source_coupled_test.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/sql_requires_injected_pool_owner.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-0.73.2 → sarj_python_lint-0.74.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.74.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/
|
|
@@ -19,6 +19,7 @@ from sarj_python_lint.rules.duplicated_override_docstring import (
|
|
|
19
19
|
)
|
|
20
20
|
from sarj_python_lint.rules.fastapi_openapi_contract import FastapiOpenapiContract
|
|
21
21
|
from sarj_python_lint.rules.fixture_returns_bare_tuple import FixtureReturnsBareTuple
|
|
22
|
+
from sarj_python_lint.rules.get_delegates_to_get_many import GetDelegatesToGetMany
|
|
22
23
|
from sarj_python_lint.rules.iac_source_coupled_test import IacSourceCoupledTest
|
|
23
24
|
from sarj_python_lint.rules.invalid_pydantic_field_default import (
|
|
24
25
|
InvalidPydanticFieldDefault,
|
|
@@ -37,7 +38,6 @@ from sarj_python_lint.rules.no_cors_wildcard_with_credentials import (
|
|
|
37
38
|
)
|
|
38
39
|
from sarj_python_lint.rules.no_duplicate_dunder_all_entry import NoDuplicateDunderAllEntry
|
|
39
40
|
from sarj_python_lint.rules.no_file_level_escape_hatch_noqa import NoFileLevelEscapeHatchNoqa
|
|
40
|
-
from sarj_python_lint.rules.no_file_level_suppression import NoFileLevelSuppression
|
|
41
41
|
from sarj_python_lint.rules.no_first_party_private_import import (
|
|
42
42
|
NoFirstPartyPrivateImport,
|
|
43
43
|
)
|
|
@@ -180,12 +180,12 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
180
180
|
PreferMatchAssertNever.id: PreferMatchAssertNever,
|
|
181
181
|
RequireKeywordOnlySwapProneParams.id: RequireKeywordOnlySwapProneParams,
|
|
182
182
|
RequirePydanticForExternalJson.id: RequirePydanticForExternalJson,
|
|
183
|
-
NoFileLevelSuppression.id: NoFileLevelSuppression,
|
|
184
183
|
PreferModuleLevelConstant.id: PreferModuleLevelConstant,
|
|
185
184
|
PreferImmutableModuleConstant.id: PreferImmutableModuleConstant,
|
|
186
185
|
MockWithoutSpec.id: MockWithoutSpec,
|
|
187
186
|
OpaqueParametrizeCaseNeedsId.id: OpaqueParametrizeCaseNeedsId,
|
|
188
187
|
FixtureReturnsBareTuple.id: FixtureReturnsBareTuple,
|
|
188
|
+
GetDelegatesToGetMany.id: GetDelegatesToGetMany,
|
|
189
189
|
KwargHeavyConstructionInTest.id: KwargHeavyConstructionInTest,
|
|
190
190
|
DefectXfailRequiresStrict.id: DefectXfailRequiresStrict,
|
|
191
191
|
NoFirstPartyPrivateImport.id: NoFirstPartyPrivateImport,
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from typing import TYPE_CHECKING, ClassVar, NamedTuple, 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
|
+
parse_or_none,
|
|
18
|
+
)
|
|
19
|
+
from sarj_python_lint.rules._paths import is_generated
|
|
20
|
+
from sarj_python_lint.rules._sql import is_store_module
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_BULK_NAMES = frozenset({"get_by_ids", "get_many"})
|
|
28
|
+
_BRANCH_NODES = (ast.If, ast.IfExp, ast.Match, ast.Try, ast.TryStar)
|
|
29
|
+
_PAIR_SIZE = 2
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _MethodPair(NamedTuple):
|
|
33
|
+
singleton: ast.FunctionDef | ast.AsyncFunctionDef
|
|
34
|
+
bulk: ast.FunctionDef | ast.AsyncFunctionDef
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _BulkResultTypes(NamedTuple):
|
|
38
|
+
key: ast.expr | None
|
|
39
|
+
value: ast.expr
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@final
|
|
43
|
+
class GetDelegatesToGetMany(Rule):
|
|
44
|
+
id = "get-delegates-to-get-many"
|
|
45
|
+
code = "SARJ421"
|
|
46
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
47
|
+
summary="Require compatible singleton store reads to delegate to their bulk implementation.",
|
|
48
|
+
rationale=(
|
|
49
|
+
"Independent singleton and bulk queries can drift in filtering, row conversion, authorization, "
|
|
50
|
+
"and missing-row behavior while maintaining two database access paths."
|
|
51
|
+
),
|
|
52
|
+
remediation=(
|
|
53
|
+
"Implement `get` through the compatible `get_many([key])` or `get_by_ids([key])` method and "
|
|
54
|
+
"project its documented zero-or-one result."
|
|
55
|
+
),
|
|
56
|
+
category=RuleCategory.MAINTAINABILITY,
|
|
57
|
+
autofix=AutofixPolicy.NONE,
|
|
58
|
+
limitations=(
|
|
59
|
+
"Only concrete methods declared together in a production store module are inspected.",
|
|
60
|
+
"The methods must have one typed key, a list result for get_many or dict result for get_by_ids, and the same sync shape.",
|
|
61
|
+
"Branching singleton implementations are excluded because caching, locking, validation, or authorization may differ intentionally.",
|
|
62
|
+
),
|
|
63
|
+
examples=(
|
|
64
|
+
RuleExample(
|
|
65
|
+
example_id="duplicate-singleton-query",
|
|
66
|
+
title="Do not maintain a second singleton query path",
|
|
67
|
+
outcome=ExampleOutcome.MATCH,
|
|
68
|
+
files=(
|
|
69
|
+
ExampleFile.python(
|
|
70
|
+
"app/user_store.py",
|
|
71
|
+
"class UserStore:\n"
|
|
72
|
+
" async def get(self, user_id: UserId) -> User | None:\n"
|
|
73
|
+
" return await self.query_one(user_id)\n\n"
|
|
74
|
+
" async def get_many(self, user_ids: list[UserId]) -> list[User]:\n"
|
|
75
|
+
" return await self.query_many(user_ids)\n",
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
focus_path=PurePosixPath("app/user_store.py"),
|
|
79
|
+
expected_count=1,
|
|
80
|
+
public=True,
|
|
81
|
+
),
|
|
82
|
+
RuleExample(
|
|
83
|
+
example_id="singleton-delegates",
|
|
84
|
+
title="Delegate the singleton read to the bulk implementation",
|
|
85
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
86
|
+
files=(
|
|
87
|
+
ExampleFile.python(
|
|
88
|
+
"app/user_store.py",
|
|
89
|
+
"class UserStore:\n"
|
|
90
|
+
" async def get(self, user_id: UserId) -> User | None:\n"
|
|
91
|
+
" rows = await self.get_many([user_id])\n"
|
|
92
|
+
" return rows[0] if rows else None\n\n"
|
|
93
|
+
" async def get_many(self, user_ids: list[UserId]) -> list[User]:\n"
|
|
94
|
+
" return await self.query_many(user_ids)\n",
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
focus_path=PurePosixPath("app/user_store.py"),
|
|
98
|
+
expected_count=0,
|
|
99
|
+
public=True,
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
description = documentation.summary
|
|
104
|
+
|
|
105
|
+
@override
|
|
106
|
+
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
107
|
+
if not is_store_module(path) or is_generated(path, source):
|
|
108
|
+
return []
|
|
109
|
+
tree = parse_or_none(path, source)
|
|
110
|
+
if tree is None:
|
|
111
|
+
return []
|
|
112
|
+
diagnostics: list[Diagnostic] = []
|
|
113
|
+
for statement in tree.body:
|
|
114
|
+
if not isinstance(statement, ast.ClassDef):
|
|
115
|
+
continue
|
|
116
|
+
pair = _compatible_pair(statement)
|
|
117
|
+
if pair is None:
|
|
118
|
+
continue
|
|
119
|
+
singleton, bulk = pair
|
|
120
|
+
if _has_branch(singleton) or _calls_method(singleton, bulk.name) or _calls_method(bulk, "get"):
|
|
121
|
+
continue
|
|
122
|
+
diagnostics.append(
|
|
123
|
+
Diagnostic(
|
|
124
|
+
path=path,
|
|
125
|
+
line=singleton.lineno,
|
|
126
|
+
col=singleton.col_offset + 1,
|
|
127
|
+
code=self.code,
|
|
128
|
+
message=(
|
|
129
|
+
f"This store defines compatible `get` and `{bulk.name}` methods; implement `get` through "
|
|
130
|
+
f"`{bulk.name}([key])` so singleton and bulk reads share one contract, or document why "
|
|
131
|
+
"their semantics differ."
|
|
132
|
+
),
|
|
133
|
+
severity=Severity.WARNING,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
return diagnostics
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _compatible_pair(
|
|
140
|
+
owner: ast.ClassDef,
|
|
141
|
+
) -> _MethodPair | None:
|
|
142
|
+
methods = [node for node in owner.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
|
|
143
|
+
singletons = [node for node in methods if node.name == "get" and _is_concrete(node)]
|
|
144
|
+
bulks = [node for node in methods if node.name in _BULK_NAMES and _is_concrete(node)]
|
|
145
|
+
if len(singletons) != 1 or len(bulks) != 1:
|
|
146
|
+
return None
|
|
147
|
+
singleton, bulk = singletons[0], bulks[0]
|
|
148
|
+
if isinstance(singleton, ast.AsyncFunctionDef) is not isinstance(bulk, ast.AsyncFunctionDef):
|
|
149
|
+
return None
|
|
150
|
+
singleton_key = _single_key_annotation(singleton)
|
|
151
|
+
bulk_key = _bulk_key_annotation(bulk)
|
|
152
|
+
singleton_value = _nullable_value(singleton.returns)
|
|
153
|
+
bulk_types = _bulk_result_types(bulk.returns, bulk.name)
|
|
154
|
+
if singleton_key is None or bulk_key is None or singleton_value is None or bulk_types is None:
|
|
155
|
+
return None
|
|
156
|
+
result_key, result_value = bulk_types
|
|
157
|
+
if ast.dump(singleton_key, include_attributes=False) != ast.dump(bulk_key, include_attributes=False):
|
|
158
|
+
return None
|
|
159
|
+
if result_key is not None and ast.dump(singleton_key, include_attributes=False) != ast.dump(
|
|
160
|
+
result_key, include_attributes=False
|
|
161
|
+
):
|
|
162
|
+
return None
|
|
163
|
+
if ast.dump(singleton_value, include_attributes=False) != ast.dump(result_value, include_attributes=False):
|
|
164
|
+
return None
|
|
165
|
+
return _MethodPair(singleton, bulk)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _is_concrete(method: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
169
|
+
if any(
|
|
170
|
+
_qualified_name(decorator).split(".")[-1] in {"abstractmethod", "overload"}
|
|
171
|
+
for decorator in method.decorator_list
|
|
172
|
+
):
|
|
173
|
+
return False
|
|
174
|
+
return not (
|
|
175
|
+
len(method.body) == 1
|
|
176
|
+
and (
|
|
177
|
+
isinstance(method.body[0], ast.Pass)
|
|
178
|
+
or (
|
|
179
|
+
isinstance(method.body[0], ast.Expr)
|
|
180
|
+
and isinstance(method.body[0].value, ast.Constant)
|
|
181
|
+
and method.body[0].value.value is Ellipsis
|
|
182
|
+
)
|
|
183
|
+
or isinstance(method.body[0], ast.Raise)
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _single_key_annotation(method: ast.FunctionDef | ast.AsyncFunctionDef) -> ast.expr | None:
|
|
189
|
+
if method.args.posonlyargs or method.args.vararg or method.args.kwarg or method.args.kwonlyargs:
|
|
190
|
+
return None
|
|
191
|
+
if len(method.args.args) != _PAIR_SIZE or method.args.defaults:
|
|
192
|
+
return None
|
|
193
|
+
self_arg, key_arg = method.args.args
|
|
194
|
+
if self_arg.arg not in {"self", "cls"}:
|
|
195
|
+
return None
|
|
196
|
+
return key_arg.annotation
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _bulk_key_annotation(method: ast.FunctionDef | ast.AsyncFunctionDef) -> ast.expr | None:
|
|
200
|
+
annotation = _single_key_annotation(method)
|
|
201
|
+
if isinstance(annotation, ast.Subscript) and _qualified_name(annotation.value) in {
|
|
202
|
+
"List",
|
|
203
|
+
"Sequence",
|
|
204
|
+
"list",
|
|
205
|
+
"typing.List",
|
|
206
|
+
"typing.Sequence",
|
|
207
|
+
}:
|
|
208
|
+
return annotation.slice
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _nullable_value(annotation: ast.expr | None) -> ast.expr | None:
|
|
213
|
+
if isinstance(annotation, ast.Subscript) and _qualified_name(annotation.value) in {"Optional", "typing.Optional"}:
|
|
214
|
+
return annotation.slice
|
|
215
|
+
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
|
|
216
|
+
if _is_none_annotation(annotation.left):
|
|
217
|
+
return annotation.right
|
|
218
|
+
if _is_none_annotation(annotation.right):
|
|
219
|
+
return annotation.left
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _bulk_result_types(annotation: ast.expr | None, name: str) -> _BulkResultTypes | None:
|
|
224
|
+
if not isinstance(annotation, ast.Subscript):
|
|
225
|
+
return None
|
|
226
|
+
base = _qualified_name(annotation.value)
|
|
227
|
+
if name == "get_many" and base in {"List", "list", "typing.List"}:
|
|
228
|
+
return _BulkResultTypes(None, annotation.slice)
|
|
229
|
+
if (
|
|
230
|
+
name == "get_by_ids"
|
|
231
|
+
and base in {"Dict", "dict", "typing.Dict"}
|
|
232
|
+
and isinstance(annotation.slice, ast.Tuple)
|
|
233
|
+
and len(annotation.slice.elts) == _PAIR_SIZE
|
|
234
|
+
):
|
|
235
|
+
return _BulkResultTypes(annotation.slice.elts[0], annotation.slice.elts[1])
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _has_branch(method: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
240
|
+
return any(isinstance(node, _BRANCH_NODES) for node in _method_nodes(method))
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _calls_method(
|
|
244
|
+
method: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
245
|
+
called_name: str,
|
|
246
|
+
) -> bool:
|
|
247
|
+
for node in _method_nodes(method):
|
|
248
|
+
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Attribute):
|
|
249
|
+
continue
|
|
250
|
+
if (
|
|
251
|
+
not isinstance(node.func.value, ast.Name)
|
|
252
|
+
or node.func.value.id not in {"cls", "self"}
|
|
253
|
+
or node.func.attr != called_name
|
|
254
|
+
):
|
|
255
|
+
continue
|
|
256
|
+
return True
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _method_nodes(method: ast.FunctionDef | ast.AsyncFunctionDef) -> list[ast.AST]:
|
|
261
|
+
nodes: list[ast.AST] = []
|
|
262
|
+
stack: list[ast.AST] = [*reversed(method.body)]
|
|
263
|
+
while stack:
|
|
264
|
+
node = stack.pop()
|
|
265
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda, ast.ClassDef)):
|
|
266
|
+
continue
|
|
267
|
+
nodes.append(node)
|
|
268
|
+
stack.extend(reversed(list(ast.iter_child_nodes(node))))
|
|
269
|
+
return nodes
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _qualified_name(node: ast.expr) -> str:
|
|
273
|
+
if isinstance(node, ast.Name):
|
|
274
|
+
return node.id
|
|
275
|
+
if isinstance(node, ast.Attribute):
|
|
276
|
+
prefix = _qualified_name(node.value)
|
|
277
|
+
return f"{prefix}.{node.attr}" if prefix else node.attr
|
|
278
|
+
return ""
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _is_none_annotation(node: ast.expr) -> bool:
|
|
282
|
+
return isinstance(node, ast.Constant) and node.value is None
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import PurePosixPath
|
|
4
|
-
import re
|
|
5
|
-
from typing import TYPE_CHECKING, ClassVar, override
|
|
6
|
-
|
|
7
|
-
from sarj_python_lint.rule_base import (
|
|
8
|
-
ColumnEncoding,
|
|
9
|
-
Diagnostic,
|
|
10
|
-
ExampleFile,
|
|
11
|
-
ExampleOutcome,
|
|
12
|
-
Rule,
|
|
13
|
-
RuleCategory,
|
|
14
|
-
RuleDocumentation,
|
|
15
|
-
RuleExample,
|
|
16
|
-
)
|
|
17
|
-
from sarj_python_lint.rules._suppression_comments import Comment, scan_comments_or_none
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# Match directive heads using each type checker's accepted spelling. Ruff's
|
|
25
|
-
# file-level blanket is already owned by PGH004.
|
|
26
|
-
_TYPE_IGNORE_RE = re.compile(r"^type:\s*ignore(?P<rest>.*)")
|
|
27
|
-
_PYRIGHT_IGNORE_RE = re.compile(r"^pyright:\s*ignore(?P<rest>.*)")
|
|
28
|
-
|
|
29
|
-
# Require a scoped rule list after directive heads that support one.
|
|
30
|
-
_BRACKET_CODES_RE = re.compile(r"^\s*\[\s*\w")
|
|
31
|
-
|
|
32
|
-
# `type: ignored` is a different word, not the directive.
|
|
33
|
-
_WORD_CONTINUATION_RE = re.compile(r"^\w")
|
|
34
|
-
|
|
35
|
-
_TYPE_IGNORE_MESSAGE = (
|
|
36
|
-
"file-level `# type: ignore` silences every mypy error in this file, "
|
|
37
|
-
"including ones added later — scope it (`# type: ignore[attr-defined]`) "
|
|
38
|
-
"or fix the findings."
|
|
39
|
-
)
|
|
40
|
-
_PYRIGHT_IGNORE_MESSAGE = (
|
|
41
|
-
"file-level `# pyright: ignore` silences every pyright diagnostic in this file, "
|
|
42
|
-
"including ones added later — scope it (`# pyright: ignore[reportUnusedImport]`) "
|
|
43
|
-
"or fix the findings."
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class NoFileLevelSuppression(Rule):
|
|
48
|
-
id: str = "no-file-level-suppression"
|
|
49
|
-
code: str = "SARJ038"
|
|
50
|
-
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
51
|
-
summary="Unscoped file-level type-checker suppressions hide every current and future diagnostic.",
|
|
52
|
-
rationale="Mypy and Pyright blankets also hide diagnostics introduced by future tool upgrades.",
|
|
53
|
-
remediation="Fix the findings or limit the directive to the specific diagnostic codes being suppressed.",
|
|
54
|
-
category=RuleCategory.MAINTAINABILITY,
|
|
55
|
-
limitations=(
|
|
56
|
-
"Only mypy-compatible `type: ignore` and Pyright file-level directives are analyzed.",
|
|
57
|
-
"Ruff's `# ruff: noqa` blanket is owned by Ruff PGH004.",
|
|
58
|
-
"Trailing per-line suppressions and directives with an explicit code list are allowed.",
|
|
59
|
-
),
|
|
60
|
-
examples=(
|
|
61
|
-
RuleExample(
|
|
62
|
-
example_id="unscoped-type-checker-suppression",
|
|
63
|
-
title="Mypy disabled for the file",
|
|
64
|
-
outcome=ExampleOutcome.MATCH,
|
|
65
|
-
files=(ExampleFile.python("service.py", "# type: ignore\nimport os\n"),),
|
|
66
|
-
focus_path=PurePosixPath("service.py"),
|
|
67
|
-
expected_count=1,
|
|
68
|
-
public=True,
|
|
69
|
-
),
|
|
70
|
-
RuleExample(
|
|
71
|
-
example_id="scoped-type-checker-suppression",
|
|
72
|
-
title="Mypy suppression names its code",
|
|
73
|
-
outcome=ExampleOutcome.NO_MATCH,
|
|
74
|
-
files=(ExampleFile.python("service.py", "# type: ignore[import-not-found]\nimport os\n"),),
|
|
75
|
-
focus_path=PurePosixPath("service.py"),
|
|
76
|
-
expected_count=0,
|
|
77
|
-
public=True,
|
|
78
|
-
),
|
|
79
|
-
),
|
|
80
|
-
)
|
|
81
|
-
description: str = documentation.summary
|
|
82
|
-
|
|
83
|
-
@override
|
|
84
|
-
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
85
|
-
comments = scan_comments_or_none(source)
|
|
86
|
-
if comments is None:
|
|
87
|
-
return []
|
|
88
|
-
diags = [
|
|
89
|
-
Diagnostic(
|
|
90
|
-
path=path,
|
|
91
|
-
line=comment.line,
|
|
92
|
-
col=comment.col,
|
|
93
|
-
code=self.code,
|
|
94
|
-
message=message,
|
|
95
|
-
column_encoding=ColumnEncoding.CODEPOINTS,
|
|
96
|
-
)
|
|
97
|
-
for comment in comments
|
|
98
|
-
if (message := _blanket_message(comment)) is not None
|
|
99
|
-
]
|
|
100
|
-
diags.sort(key=lambda d: (d.line, d.col))
|
|
101
|
-
return diags
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def _blanket_message(comment: Comment) -> str | None:
|
|
105
|
-
if not (comment.standalone and comment.before_first_statement):
|
|
106
|
-
return None
|
|
107
|
-
if _is_unscoped(comment.body, _TYPE_IGNORE_RE, _BRACKET_CODES_RE):
|
|
108
|
-
return _TYPE_IGNORE_MESSAGE
|
|
109
|
-
if _is_unscoped(comment.body, _PYRIGHT_IGNORE_RE, _BRACKET_CODES_RE):
|
|
110
|
-
return _PYRIGHT_IGNORE_MESSAGE
|
|
111
|
-
return None
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def _is_unscoped(body: str, directive: re.Pattern[str], codes: re.Pattern[str]) -> bool:
|
|
115
|
-
match = directive.match(body)
|
|
116
|
-
if match is None:
|
|
117
|
-
return False
|
|
118
|
-
rest = match["rest"]
|
|
119
|
-
if _WORD_CONTINUATION_RE.match(rest):
|
|
120
|
-
return False
|
|
121
|
-
return not codes.match(rest)
|
|
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.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.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.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.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.73.2 → sarj_python_lint-0.74.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
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.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.73.2 → sarj_python_lint-0.74.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.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/no_select_star.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.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.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.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.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.73.2 → sarj_python_lint-0.74.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.73.2 → sarj_python_lint-0.74.0}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|