sarj-python-lint 0.42.0__tar.gz → 0.43.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.42.0 → sarj_python_lint-0.43.0}/PKG-INFO +1 -1
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/pyproject.toml +1 -1
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_registry.py +2 -0
- sarj_python_lint-0.43.0/src/sarj_python_lint/rules/prefer_nominal_id_types.py +219 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/.gitignore +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/README.md +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/conditional_assertion_in_test.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/duplicated_override_docstring.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/inefficient_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/interaction_only_test.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/kwonly_same_type_params.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_fat_try_blocks.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_file_level_suppression.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_fstring_in_log.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_long_comment.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_raw_sql_in_tests.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_sequential_await.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_sleep_in_test_body.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_unreachable_after_terminal.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_match_pattern_destructuring.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_real_store_in_tests.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_single_sentence_comment.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/single_public_export.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/sleep_with_computed_arg_in_test.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/tautological_mock_assertion.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/test_loops_over_literal_cases.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/xfail_requires_strict.py +0 -0
- {sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/zero_assertion_test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.43.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://github.com/sarj-ai/standards/tree/main/packages/python
|
|
6
6
|
Project-URL: Repository, https://github.com/sarj-ai/standards
|
|
@@ -75,6 +75,7 @@ from sarj_python_lint.rules.prefer_module_level_constant import (
|
|
|
75
75
|
from sarj_python_lint.rules.prefer_namedtuple_over_tuple_return import (
|
|
76
76
|
PreferNamedtupleOverTupleReturn,
|
|
77
77
|
)
|
|
78
|
+
from sarj_python_lint.rules.prefer_nominal_id_types import PreferNominalIdTypes
|
|
78
79
|
from sarj_python_lint.rules.prefer_non_nullable_collection import (
|
|
79
80
|
PreferNonNullableCollection,
|
|
80
81
|
)
|
|
@@ -196,6 +197,7 @@ REGISTRY: dict[str, type[Rule]] = {
|
|
|
196
197
|
PreferSingleSentenceComment.id: PreferSingleSentenceComment,
|
|
197
198
|
NoLongComment.id: NoLongComment,
|
|
198
199
|
NoTypedDocSections.id: NoTypedDocSections,
|
|
200
|
+
PreferNominalIdTypes.id: PreferNominalIdTypes,
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
__all__ = ["REGISTRY"]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""SARJ093 flags boundaries where multiple ID roles share primitive string types.
|
|
2
|
+
|
|
3
|
+
Examples: https://github.com/sarj-ai/standards/blob/main/packages/python/tests/rules/test_prefer_nominal_id_types.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import TYPE_CHECKING, override
|
|
11
|
+
|
|
12
|
+
from sarj_python_lint.rule_base import Diagnostic, Rule, is_suppressed, parse_or_none
|
|
13
|
+
from sarj_python_lint.rules._ast_index import walk
|
|
14
|
+
from sarj_python_lint.rules._paths import is_generated, is_test_path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_TRANSPARENT_WRAPPERS = frozenset({"List", "Mapped", "Optional", "Sequence", "list"})
|
|
22
|
+
_UNION_WRAPPERS = frozenset({"Union"})
|
|
23
|
+
_MIN_ID_ROLES = 2
|
|
24
|
+
_OPERATIONAL_IDS = frozenset(
|
|
25
|
+
{
|
|
26
|
+
"correlation_id",
|
|
27
|
+
"event_id",
|
|
28
|
+
"job_id",
|
|
29
|
+
"message_id",
|
|
30
|
+
"operation_id",
|
|
31
|
+
"request_id",
|
|
32
|
+
"span_id",
|
|
33
|
+
"task_id",
|
|
34
|
+
"trace_id",
|
|
35
|
+
"unique_id",
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
_MIGRATION_PARTS = frozenset({"alembic", "migrations", "versions"})
|
|
39
|
+
_NON_PRODUCTION_PARTS = frozenset({"fixtures", "scripts", "test_fakes", "testing"})
|
|
40
|
+
_RAW_SCHEMA_SUFFIXES = ("Config", "Credentials", "Settings")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True, slots=True)
|
|
44
|
+
class _IdRole:
|
|
45
|
+
name: str
|
|
46
|
+
annotation: ast.expr
|
|
47
|
+
raw_string: bool
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PreferNominalIdTypes(Rule):
|
|
51
|
+
id: str = "prefer-nominal-id-types"
|
|
52
|
+
code: str = "SARJ093"
|
|
53
|
+
description: str = (
|
|
54
|
+
"A production boundary with multiple ID-shaped roles should use nominal ID types, not primitive strings."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@override
|
|
58
|
+
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
59
|
+
if (
|
|
60
|
+
is_test_path(path)
|
|
61
|
+
or is_generated(path, source)
|
|
62
|
+
or any(part.lower() in _MIGRATION_PARTS for part in path.parts)
|
|
63
|
+
or any(part.lower() in _NON_PRODUCTION_PARTS for part in path.parts)
|
|
64
|
+
or _is_external_adapter_path(path)
|
|
65
|
+
):
|
|
66
|
+
return []
|
|
67
|
+
tree = parse_or_none(path, source)
|
|
68
|
+
if tree is None:
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
source_lines = source.splitlines()
|
|
72
|
+
diagnostics: list[Diagnostic] = []
|
|
73
|
+
exempt_schema_nodes = {
|
|
74
|
+
descendant
|
|
75
|
+
for candidate in ast.walk(tree)
|
|
76
|
+
if isinstance(candidate, ast.ClassDef) and _is_raw_schema_class(candidate)
|
|
77
|
+
for descendant in ast.walk(candidate)
|
|
78
|
+
if descendant is not candidate
|
|
79
|
+
}
|
|
80
|
+
for node in walk(tree):
|
|
81
|
+
if node in exempt_schema_nodes:
|
|
82
|
+
continue
|
|
83
|
+
roles = _boundary_roles(node)
|
|
84
|
+
raw = [role for role in roles if role.raw_string]
|
|
85
|
+
if not raw or len({role.name for role in roles}) < _MIN_ID_ROLES:
|
|
86
|
+
continue
|
|
87
|
+
first = raw[0]
|
|
88
|
+
if is_suppressed(source_lines, first.annotation.lineno, self.code):
|
|
89
|
+
continue
|
|
90
|
+
names = ", ".join(f"`{role.name}`" for role in roles)
|
|
91
|
+
diagnostics.append(
|
|
92
|
+
Diagnostic(
|
|
93
|
+
path=path,
|
|
94
|
+
line=first.annotation.lineno,
|
|
95
|
+
col=first.annotation.col_offset + 1,
|
|
96
|
+
code=self.code,
|
|
97
|
+
message=(
|
|
98
|
+
f"{names} are multiple ID-shaped roles at one boundary, but at least one uses primitive `str`; "
|
|
99
|
+
"introduce or reuse `NewType` identifier types and propagate them through the boundary."
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
return sorted(diagnostics, key=lambda diagnostic: (diagnostic.line, diagnostic.col))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _boundary_roles(node: ast.AST) -> list[_IdRole]:
|
|
107
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
108
|
+
if node.name.startswith("_") and node.name != "__init__":
|
|
109
|
+
return []
|
|
110
|
+
arguments = [*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs]
|
|
111
|
+
return [role for argument in arguments if (role := _role(argument.arg, argument.annotation)) is not None]
|
|
112
|
+
if isinstance(node, ast.ClassDef):
|
|
113
|
+
if _is_raw_schema_class(node):
|
|
114
|
+
return []
|
|
115
|
+
return [
|
|
116
|
+
role
|
|
117
|
+
for statement in node.body
|
|
118
|
+
if isinstance(statement, ast.AnnAssign) and isinstance(statement.target, ast.Name)
|
|
119
|
+
if (role := _role(statement.target.id, statement.annotation, allow_bare_id=True)) is not None
|
|
120
|
+
]
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _role(name: str, annotation: ast.expr | None, *, allow_bare_id: bool = False) -> _IdRole | None:
|
|
125
|
+
if annotation is None or name in _OPERATIONAL_IDS:
|
|
126
|
+
return None
|
|
127
|
+
if not (name.endswith(("_id", "_ids")) or (allow_bare_id and name == "id")):
|
|
128
|
+
return None
|
|
129
|
+
raw_string = _is_raw_string_id(annotation)
|
|
130
|
+
if not raw_string and not _is_nominal_id(annotation):
|
|
131
|
+
return None
|
|
132
|
+
return _IdRole(name=name, annotation=annotation, raw_string=raw_string)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _qualified_tail(node: ast.expr) -> str:
|
|
136
|
+
if isinstance(node, ast.Name):
|
|
137
|
+
return node.id
|
|
138
|
+
if isinstance(node, ast.Attribute):
|
|
139
|
+
return node.attr
|
|
140
|
+
return ""
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _is_raw_schema_class(node: ast.ClassDef) -> bool:
|
|
144
|
+
return node.name.endswith(_RAW_SCHEMA_SUFFIXES) or any(
|
|
145
|
+
_qualified_tail(base) == "Protocol" for base in node.bases
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _is_external_adapter_path(path: Path) -> bool:
|
|
150
|
+
parts = {part.lower() for part in path.parts}
|
|
151
|
+
return "providers" in parts or "integration" in parts or (
|
|
152
|
+
"integrations" in parts and ("models" in parts or path.name == "models.py")
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _is_raw_string_id(annotation: ast.expr) -> bool:
|
|
157
|
+
if isinstance(annotation, ast.Name):
|
|
158
|
+
return annotation.id == "str"
|
|
159
|
+
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
|
|
160
|
+
members = _flatten_union(annotation)
|
|
161
|
+
return any(_is_raw_string_id(member) for member in members) and all(
|
|
162
|
+
_is_raw_string_id(member) or _is_nominal_id(member) or _is_none(member)
|
|
163
|
+
for member in members
|
|
164
|
+
)
|
|
165
|
+
if not isinstance(annotation, ast.Subscript):
|
|
166
|
+
return False
|
|
167
|
+
wrapper = _qualified_tail(annotation.value)
|
|
168
|
+
if wrapper == "Annotated":
|
|
169
|
+
return (
|
|
170
|
+
isinstance(annotation.slice, ast.Tuple)
|
|
171
|
+
and bool(annotation.slice.elts)
|
|
172
|
+
and _is_raw_string_id(annotation.slice.elts[0])
|
|
173
|
+
)
|
|
174
|
+
if wrapper in _TRANSPARENT_WRAPPERS:
|
|
175
|
+
return _is_raw_string_id(annotation.slice)
|
|
176
|
+
if wrapper in _UNION_WRAPPERS:
|
|
177
|
+
members = list(annotation.slice.elts) if isinstance(annotation.slice, ast.Tuple) else [annotation.slice]
|
|
178
|
+
return any(_is_raw_string_id(member) for member in members) and all(
|
|
179
|
+
_is_raw_string_id(member) or _is_nominal_id(member) or _is_none(member)
|
|
180
|
+
for member in members
|
|
181
|
+
)
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _flatten_union(annotation: ast.expr) -> list[ast.expr]:
|
|
186
|
+
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
|
|
187
|
+
return [*_flatten_union(annotation.left), *_flatten_union(annotation.right)]
|
|
188
|
+
return [annotation]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _is_none(annotation: ast.expr) -> bool:
|
|
192
|
+
return (isinstance(annotation, ast.Constant) and annotation.value is None) or (
|
|
193
|
+
isinstance(annotation, ast.Name) and annotation.id == "None"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _is_nominal_id(annotation: ast.expr) -> bool:
|
|
198
|
+
if isinstance(annotation, (ast.Name, ast.Attribute)):
|
|
199
|
+
tail = _qualified_tail(annotation)
|
|
200
|
+
return tail.endswith(("Id", "ID"))
|
|
201
|
+
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
|
|
202
|
+
members = _flatten_union(annotation)
|
|
203
|
+
return any(_is_nominal_id(member) for member in members) and all(
|
|
204
|
+
_is_nominal_id(member) or _is_none(member) for member in members
|
|
205
|
+
)
|
|
206
|
+
if isinstance(annotation, ast.Subscript) and _qualified_tail(annotation.value) in _TRANSPARENT_WRAPPERS:
|
|
207
|
+
return _is_nominal_id(annotation.slice)
|
|
208
|
+
if (
|
|
209
|
+
isinstance(annotation, ast.Subscript)
|
|
210
|
+
and _qualified_tail(annotation.value) == "Annotated"
|
|
211
|
+
and isinstance(annotation.slice, ast.Tuple)
|
|
212
|
+
):
|
|
213
|
+
return bool(annotation.slice.elts) and _is_nominal_id(annotation.slice.elts[0])
|
|
214
|
+
if isinstance(annotation, ast.Subscript) and _qualified_tail(annotation.value) in _UNION_WRAPPERS:
|
|
215
|
+
members = list(annotation.slice.elts) if isinstance(annotation.slice, ast.Tuple) else [annotation.slice]
|
|
216
|
+
return any(_is_nominal_id(member) for member in members) and all(
|
|
217
|
+
_is_nominal_id(member) or _is_none(member) for member in members
|
|
218
|
+
)
|
|
219
|
+
return False
|
|
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.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/_prose_budget.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
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_fat_try_blocks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_fstring_in_log.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.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
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_select_star.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/no_stdlib_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.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
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.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
|
{sarj_python_lint-0.42.0 → sarj_python_lint-0.43.0}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|