sarj-python-lint 0.31.0__tar.gz → 0.32.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.31.0 → sarj_python_lint-0.32.0}/PKG-INFO +1 -1
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/pyproject.toml +1 -1
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/mock_without_spec.py +127 -3
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/.gitignore +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/README.md +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/conditional_assertion_in_test.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/inefficient_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/interaction_only_test.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/kwonly_same_type_params.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_fat_try_blocks.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_file_level_suppression.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_fstring_in_log.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_implicit_attribute_access.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_patching_system_under_test.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_raw_sql_in_tests.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_sequential_await.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_sleep_in_test_body.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_unreachable_after_terminal.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_match_pattern_destructuring.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_real_store_in_tests.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/single_public_export.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/sleep_with_computed_arg_in_test.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/tautological_mock_assertion.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/test_loops_over_literal_cases.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/xfail_requires_strict.py +0 -0
- {sarj_python_lint-0.31.0 → sarj_python_lint-0.32.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.32.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
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
@@ -38,7 +38,9 @@ Deliberately NOT flagged:
|
|
|
38
38
|
* `create_autospec(...)`, `mock.ANY`, `mock.sentinel`, `mock.call` — specced by
|
|
39
39
|
construction or not doubles at all,
|
|
40
40
|
* bare `Mock` referenced without being called (annotations, `isinstance`
|
|
41
|
-
checks) — only a construction can carry a spec argument
|
|
41
|
+
checks) — only a construction can carry a spec argument,
|
|
42
|
+
* a canned callable stub bound to an attribute (`receiver.method =
|
|
43
|
+
AsyncMock(return_value=...)`) — see exemption 4 below.
|
|
42
44
|
|
|
43
45
|
The import-backed name check is the load-bearing false-positive guard. Test
|
|
44
46
|
suites routinely define their own `Mock`-suffixed fakes (`MockVisionBankClient`,
|
|
@@ -92,6 +94,45 @@ of this rule reported 137 hits, of which 38 were false positives:
|
|
|
92
94
|
constructions lexically inside an `ImportError`/`ModuleNotFoundError`
|
|
93
95
|
handler.
|
|
94
96
|
|
|
97
|
+
4. **A canned callable stub bound to an attribute (146 hits, 18.8%).** Measured
|
|
98
|
+
over the two first-party repos (bulbul + noura-be, 777 hits), the single
|
|
99
|
+
largest shape was `receiver.method = Mock(...)` — 283 hits, 36.4%. It is not
|
|
100
|
+
an unspecced *collaborator*; it replaces one callable on a receiver that
|
|
101
|
+
already exists, and the contract this rule protects belongs to that receiver.
|
|
102
|
+
Either the receiver carries `spec=` — in which case production's call through
|
|
103
|
+
a renamed attribute raises `AttributeError` off the specced parent and the
|
|
104
|
+
test fails loudly, which is exactly the rot this rule wants — or the receiver
|
|
105
|
+
is itself flagged here at its own construction, and reporting the leaf as
|
|
106
|
+
well says the same thing twice. Evidence:
|
|
107
|
+
`bulbul/python/agent/tests/conftest.py:167-170`, where
|
|
108
|
+
`backchanneler = mock.Mock(spec=Backchanneler)` is followed by
|
|
109
|
+
`backchanneler.start_audio = mock.AsyncMock()` / `.run = ...` / `.stop = ...`
|
|
110
|
+
— model spec discipline, and three findings; and
|
|
111
|
+
`bulbul/python/bulbul/tests/integrations/test_zoho_notifications_handler.py:86`
|
|
112
|
+
(`crm_service.get_record = mock.AsyncMock(return_value=record)`), where the
|
|
113
|
+
only thing the file reads off `crm_service.get_record` is
|
|
114
|
+
`assert_awaited_once_with`. `AsyncMock` dominates the shape (164 of 283)
|
|
115
|
+
because `Mock(spec=X)` children are not awaitable, so a specced double *must*
|
|
116
|
+
have its async methods stubbed this way to be usable at all.
|
|
117
|
+
|
|
118
|
+
The guard demands POSITIVE evidence of callability — a canned
|
|
119
|
+
`return_value=`/`side_effect=`, a mock-API read off the path
|
|
120
|
+
(`recv.method.assert_called_once_with(...)`), or an invocation — on top of
|
|
121
|
+
the absence of any domain-attribute read. Absence alone is not enough:
|
|
122
|
+
`agent/tests/test_for_call_settings.py:64` (`room.local_participant =
|
|
123
|
+
mock.Mock()`) is an object double this file never happens to walk, and it
|
|
124
|
+
stays flagged, as do the 60 namespace doubles the corpus does walk —
|
|
125
|
+
`test_for_call_settings.py:71,74,76` (`api.room` / `api.sip` / `api.egress`,
|
|
126
|
+
read back as `.delete_room`, `.create_sip_participant`,
|
|
127
|
+
`.start_room_composite_egress`) and `agent/tests/conftest.py:246-247`
|
|
128
|
+
(`job_context.api`, `job_context.api.sip`).
|
|
129
|
+
|
|
130
|
+
What this gives up is arity: `AsyncMock(spec=Store.get)` would reject a call
|
|
131
|
+
whose signature no longer matches, and the exempted stubs will not. That is
|
|
132
|
+
the narrower half of the defect, and it is the half `patch.object(mod,
|
|
133
|
+
"func")` still gets flagged for, since `autospec=True` is a one-word fix
|
|
134
|
+
there and there is no equivalent for a raw attribute assignment.
|
|
135
|
+
|
|
95
136
|
The 99 survivors are true positives: unspecced `MagicMock()` doubles for real
|
|
96
137
|
types (`black/tests/test_black.py:2933`, a `MagicMock()` standing in for `Path`
|
|
97
138
|
and answering `.relative_to`/`.resolve`/`.is_dir`/`.is_file`), and
|
|
@@ -175,6 +216,11 @@ _MOCK_API_ATTRS = frozenset(
|
|
|
175
216
|
# An import that failed leaves nothing importable to spec against.
|
|
176
217
|
_IMPORT_FAILURES = frozenset({"ImportError", "ModuleNotFoundError"})
|
|
177
218
|
|
|
219
|
+
# Keywords that canned-answer a *call*. You only say what a double returns, or
|
|
220
|
+
# raises, when something is going to invoke it — so their presence is positive
|
|
221
|
+
# evidence that the double stands in for one callable rather than an object.
|
|
222
|
+
_CANNED_RESULT_KEYWORDS = frozenset({"return_value", "side_effect"})
|
|
223
|
+
|
|
178
224
|
|
|
179
225
|
class MockWithoutSpec(Rule):
|
|
180
226
|
"""A `unittest.mock` double built with no `spec=` accepts any attribute."""
|
|
@@ -321,6 +367,9 @@ class _FileFacts:
|
|
|
321
367
|
self.reads: dict[str, set[str]] = {}
|
|
322
368
|
self.called: set[str] = set()
|
|
323
369
|
self.import_fallbacks: set[ast.Call] = set()
|
|
370
|
+
self.attribute_target: dict[ast.Call, str] = {}
|
|
371
|
+
self.path_reads: dict[str, set[str]] = {}
|
|
372
|
+
self.path_calls: set[str] = set()
|
|
324
373
|
|
|
325
374
|
@classmethod
|
|
326
375
|
def from_tree(cls, tree: ast.Module) -> _FileFacts:
|
|
@@ -339,16 +388,39 @@ class _FileFacts:
|
|
|
339
388
|
elif isinstance(node, ast.Attribute):
|
|
340
389
|
if isinstance(node.value, ast.Name):
|
|
341
390
|
found.reads.setdefault(node.value.id, set()).add(node.attr)
|
|
391
|
+
found._record_path_read(node)
|
|
342
392
|
elif isinstance(node, ast.Call):
|
|
343
393
|
if isinstance(node.func, ast.Name):
|
|
344
394
|
found.called.add(node.func.id)
|
|
395
|
+
found._record_path_call(node)
|
|
345
396
|
elif _catches_import_failure(node):
|
|
346
397
|
found.import_fallbacks.update(child for child in walk(node) if isinstance(child, ast.Call))
|
|
347
398
|
return found
|
|
348
399
|
|
|
349
400
|
def _bind(self, target: ast.expr | None, value: ast.expr | None) -> None:
|
|
350
|
-
if
|
|
401
|
+
if not isinstance(value, ast.Call):
|
|
402
|
+
return
|
|
403
|
+
if isinstance(target, ast.Name):
|
|
351
404
|
self.bound_name[value] = target.id
|
|
405
|
+
elif isinstance(target, ast.Attribute):
|
|
406
|
+
path = _dotted_path(target)
|
|
407
|
+
if path is not None:
|
|
408
|
+
self.attribute_target[value] = path
|
|
409
|
+
|
|
410
|
+
def _record_path_read(self, node: ast.Attribute) -> None:
|
|
411
|
+
# Load context only. `recv.method = Mock()` is a *store* through
|
|
412
|
+
# `recv.method`; counting it would make every stub look like a namespace
|
|
413
|
+
# double read back through its own name.
|
|
414
|
+
if not isinstance(node.ctx, ast.Load):
|
|
415
|
+
return
|
|
416
|
+
path = _dotted_path(node.value)
|
|
417
|
+
if path is not None:
|
|
418
|
+
self.path_reads.setdefault(path, set()).add(node.attr)
|
|
419
|
+
|
|
420
|
+
def _record_path_call(self, node: ast.Call) -> None:
|
|
421
|
+
path = _dotted_path(node.func)
|
|
422
|
+
if path is not None:
|
|
423
|
+
self.path_calls.add(path)
|
|
352
424
|
|
|
353
425
|
def is_call_recorder(self, node: ast.Call) -> bool:
|
|
354
426
|
"""Report whether the double bound by `node` is only ever called and introspected.
|
|
@@ -368,6 +440,58 @@ class _FileFacts:
|
|
|
368
440
|
return False
|
|
369
441
|
return self.reads.get(name, set()) <= _MOCK_API_ATTRS
|
|
370
442
|
|
|
443
|
+
def is_method_stub(self, node: ast.Call) -> bool:
|
|
444
|
+
"""Report whether `node` is a canned stub for one method of some receiver.
|
|
445
|
+
|
|
446
|
+
`receiver.method = AsyncMock(return_value=...)` replaces a single
|
|
447
|
+
callable, not an object. The contract this rule protects belongs to the
|
|
448
|
+
*receiver* — it is the receiver's construction that either carries
|
|
449
|
+
`spec=` or is itself flagged here — and a callable has no attribute
|
|
450
|
+
surface for `spec=` to fence. Two conditions must hold:
|
|
451
|
+
|
|
452
|
+
* nothing but the mock API is read back off the assigned path, so the
|
|
453
|
+
double is not standing in for an object the test walks, and
|
|
454
|
+
* something positively marks it as a callable — a canned
|
|
455
|
+
`return_value=`/`side_effect=`, an `assert_called*`-style read, or an
|
|
456
|
+
invocation of the path.
|
|
457
|
+
|
|
458
|
+
The second condition is what keeps `room.local_participant = Mock()`
|
|
459
|
+
flagged: absence of attribute reads is not evidence of callability, and
|
|
460
|
+
a namespace double that this file happens never to walk is exactly the
|
|
461
|
+
case `spec=` exists for.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
True when the double is a canned callable stub bound to an attribute.
|
|
465
|
+
|
|
466
|
+
"""
|
|
467
|
+
path = self.attribute_target.get(node)
|
|
468
|
+
if path is None:
|
|
469
|
+
return False
|
|
470
|
+
seen = self.path_reads.get(path, set())
|
|
471
|
+
if not seen <= _MOCK_API_ATTRS:
|
|
472
|
+
return False
|
|
473
|
+
canned = any(kw.arg in _CANNED_RESULT_KEYWORDS for kw in node.keywords)
|
|
474
|
+
return canned or bool(seen) or path in self.path_calls
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _dotted_path(expr: ast.expr) -> str | None:
|
|
478
|
+
"""Render a pure `name.attr.attr` chain as a dotted string.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
The dotted path, or None when the chain is rooted in anything other than
|
|
482
|
+
a bare name (a subscript, a call, a literal), where two occurrences of
|
|
483
|
+
the same text need not denote the same object.
|
|
484
|
+
|
|
485
|
+
"""
|
|
486
|
+
parts: list[str] = []
|
|
487
|
+
while isinstance(expr, ast.Attribute):
|
|
488
|
+
parts.append(expr.attr)
|
|
489
|
+
expr = expr.value
|
|
490
|
+
if not isinstance(expr, ast.Name):
|
|
491
|
+
return None
|
|
492
|
+
parts.append(expr.id)
|
|
493
|
+
return ".".join(reversed(parts))
|
|
494
|
+
|
|
371
495
|
|
|
372
496
|
def _catches_import_failure(handler: ast.ExceptHandler) -> bool:
|
|
373
497
|
caught = handler.type
|
|
@@ -386,7 +510,7 @@ def _unspecced_calls(tree: ast.Module, names: _MockNames, facts: _FileFacts) ->
|
|
|
386
510
|
label = _render_callee(node.func, symbol)
|
|
387
511
|
if _has_spec_argument(node) or _has_positional_replacement(node, label):
|
|
388
512
|
continue
|
|
389
|
-
if node in facts.import_fallbacks or facts.is_call_recorder(node):
|
|
513
|
+
if node in facts.import_fallbacks or facts.is_call_recorder(node) or facts.is_method_stub(node):
|
|
390
514
|
continue
|
|
391
515
|
hits.append((node, label))
|
|
392
516
|
return hits
|
|
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.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/_first_party.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
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.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.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_fstring_in_log.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.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.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.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/no_stdlib_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.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.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.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
|
{sarj_python_lint-0.31.0 → sarj_python_lint-0.32.0}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|