sarj-python-lint 0.81.4__tar.gz → 0.81.5__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.81.4 → sarj_python_lint-0.81.5}/PKG-INFO +1 -1
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/pyproject.toml +1 -1
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_class_row.py +85 -25
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/.gitignore +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/LICENSE +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/README.md +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/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.81.
|
|
3
|
+
Version: 0.81.5
|
|
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/
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/prefer_class_row.py
RENAMED
|
@@ -36,6 +36,7 @@ _SCOPE_NODES = (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)
|
|
|
36
36
|
class _BoundCursor(NamedTuple):
|
|
37
37
|
variable: str
|
|
38
38
|
factory: ast.expr
|
|
39
|
+
active_nodes: tuple[ast.AST, ...]
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class _FetchedValue(NamedTuple):
|
|
@@ -73,7 +74,7 @@ class PreferClassRow(Rule):
|
|
|
73
74
|
files=(
|
|
74
75
|
ExampleFile.python(
|
|
75
76
|
"app/task_store.py",
|
|
76
|
-
|
|
77
|
+
'from psycopg.rows import dict_row\n\nasync def load(conn):\n async with conn.cursor(row_factory=dict_row) as cursor:\n await cursor.execute("SELECT id, state FROM task")\n row = await cursor.fetchone()\n return Task.model_validate(row)\n',
|
|
77
78
|
),
|
|
78
79
|
),
|
|
79
80
|
focus_path=PurePosixPath("app/task_store.py"),
|
|
@@ -112,7 +113,7 @@ class PreferClassRow(Rule):
|
|
|
112
113
|
for cursor in _bound_cursors(function):
|
|
113
114
|
if not imports.is_dict_row(cursor.factory, shadowed):
|
|
114
115
|
continue
|
|
115
|
-
models = _models_built_from_cursor(
|
|
116
|
+
models = _models_built_from_cursor(cursor)
|
|
116
117
|
if len(models) != 1:
|
|
117
118
|
continue
|
|
118
119
|
model = next(iter(models))
|
|
@@ -247,37 +248,78 @@ def _functions(tree: ast.Module) -> Iterator[ast.FunctionDef | ast.AsyncFunction
|
|
|
247
248
|
return (node for node in ast.walk(tree) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)))
|
|
248
249
|
|
|
249
250
|
|
|
250
|
-
def _scope_nodes(function: ast.FunctionDef | ast.AsyncFunctionDef) -> Iterator[ast.AST]:
|
|
251
|
-
pending: list[ast.AST] = list(reversed(function.body))
|
|
252
|
-
while pending:
|
|
253
|
-
node = pending.pop()
|
|
254
|
-
yield node
|
|
255
|
-
if isinstance(node, _SCOPE_NODES):
|
|
256
|
-
continue
|
|
257
|
-
pending.extend(reversed(list(ast.iter_child_nodes(node))))
|
|
258
|
-
|
|
259
|
-
|
|
260
251
|
def _bound_cursors(function: ast.FunctionDef | ast.AsyncFunctionDef) -> list[_BoundCursor]:
|
|
252
|
+
scoped_nodes = tuple(_nodes_in(function.body))
|
|
261
253
|
cursors: list[_BoundCursor] = []
|
|
262
|
-
for node in
|
|
254
|
+
for node in scoped_nodes:
|
|
263
255
|
if isinstance(node, (ast.With, ast.AsyncWith)):
|
|
264
256
|
for item in node.items:
|
|
265
257
|
if isinstance(item.optional_vars, ast.Name):
|
|
266
|
-
|
|
258
|
+
active_nodes = _active_nodes(tuple(_nodes_in(node.body)), item.optional_vars.id, node.lineno)
|
|
259
|
+
cursor = _cursor(item.context_expr, item.optional_vars.id, active_nodes)
|
|
267
260
|
if cursor is not None:
|
|
268
261
|
cursors.append(cursor)
|
|
269
262
|
elif isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
|
|
270
|
-
|
|
263
|
+
variable = node.targets[0].id
|
|
264
|
+
cursor = _cursor(node.value, variable, _active_nodes(scoped_nodes, variable, node.lineno))
|
|
271
265
|
if cursor is not None:
|
|
272
266
|
cursors.append(cursor)
|
|
273
267
|
elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.value is not None:
|
|
274
|
-
cursor = _cursor(
|
|
268
|
+
cursor = _cursor(
|
|
269
|
+
node.value,
|
|
270
|
+
node.target.id,
|
|
271
|
+
_active_nodes(scoped_nodes, node.target.id, node.lineno),
|
|
272
|
+
)
|
|
275
273
|
if cursor is not None:
|
|
276
274
|
cursors.append(cursor)
|
|
277
275
|
return cursors
|
|
278
276
|
|
|
279
277
|
|
|
280
|
-
def
|
|
278
|
+
def _nodes_in(statements: Iterable[ast.stmt]) -> Iterator[ast.AST]:
|
|
279
|
+
pending: list[ast.AST] = list(reversed(tuple(statements)))
|
|
280
|
+
while pending:
|
|
281
|
+
node = pending.pop()
|
|
282
|
+
yield node
|
|
283
|
+
if isinstance(node, _SCOPE_NODES):
|
|
284
|
+
continue
|
|
285
|
+
pending.extend(reversed(list(ast.iter_child_nodes(node))))
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _active_nodes(nodes: tuple[ast.AST, ...], variable: str, start: int) -> tuple[ast.AST, ...]:
|
|
289
|
+
rebound_lines = [
|
|
290
|
+
node.lineno
|
|
291
|
+
for node in nodes
|
|
292
|
+
if isinstance(node, ast.stmt) and node.lineno > start and _binds_name(node, variable)
|
|
293
|
+
]
|
|
294
|
+
end = min(rebound_lines, default=None)
|
|
295
|
+
return tuple(
|
|
296
|
+
node
|
|
297
|
+
for node in nodes
|
|
298
|
+
if getattr(node, "lineno", start) > start and (end is None or getattr(node, "lineno", end) < end)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _binds_name(node: ast.stmt, name: str) -> bool:
|
|
303
|
+
if _writes_name(node, name):
|
|
304
|
+
return True
|
|
305
|
+
match node:
|
|
306
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
|
|
307
|
+
return node.name == name
|
|
308
|
+
case ast.For() | ast.AsyncFor():
|
|
309
|
+
return _target_root(node.target) == name
|
|
310
|
+
case ast.With() | ast.AsyncWith():
|
|
311
|
+
return any(
|
|
312
|
+
item.optional_vars is not None and _target_root(item.optional_vars) == name for item in node.items
|
|
313
|
+
)
|
|
314
|
+
case ast.Import():
|
|
315
|
+
return any((alias.asname or alias.name.partition(".")[0]) == name for alias in node.names)
|
|
316
|
+
case ast.ImportFrom():
|
|
317
|
+
return any((alias.asname or alias.name) == name for alias in node.names)
|
|
318
|
+
case _:
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _cursor(expression: ast.expr, variable: str, active_nodes: tuple[ast.AST, ...]) -> _BoundCursor | None:
|
|
281
323
|
if not (
|
|
282
324
|
isinstance(expression, ast.Call)
|
|
283
325
|
and isinstance(expression.func, ast.Attribute)
|
|
@@ -285,18 +327,15 @@ def _cursor(expression: ast.expr, variable: str) -> _BoundCursor | None:
|
|
|
285
327
|
):
|
|
286
328
|
return None
|
|
287
329
|
factory = next((keyword.value for keyword in expression.keywords if keyword.arg == _ROW_FACTORY), None)
|
|
288
|
-
return None if factory is None else _BoundCursor(variable, factory)
|
|
330
|
+
return None if factory is None else _BoundCursor(variable, factory, active_nodes)
|
|
289
331
|
|
|
290
332
|
|
|
291
333
|
def _models_built_from_cursor(
|
|
292
|
-
|
|
293
|
-
cursor: str,
|
|
334
|
+
cursor: _BoundCursor,
|
|
294
335
|
) -> frozenset[str]:
|
|
295
|
-
scoped_nodes = list(
|
|
296
|
-
fetched_values = _fetched_values(scoped_nodes, cursor)
|
|
297
|
-
|
|
298
|
-
return frozenset()
|
|
299
|
-
models: set[str] = set()
|
|
336
|
+
scoped_nodes = list(cursor.active_nodes)
|
|
337
|
+
fetched_values = _fetched_values(scoped_nodes, cursor.variable)
|
|
338
|
+
models = _models_from_direct_fetch(scoped_nodes, cursor.variable)
|
|
300
339
|
for fetched in fetched_values:
|
|
301
340
|
if fetched.many:
|
|
302
341
|
converted = _models_from_collection(scoped_nodes, fetched)
|
|
@@ -308,6 +347,27 @@ def _models_built_from_cursor(
|
|
|
308
347
|
return frozenset(models)
|
|
309
348
|
|
|
310
349
|
|
|
350
|
+
def _models_from_direct_fetch(scoped_nodes: list[ast.AST], cursor: str) -> set[str]:
|
|
351
|
+
models: set[str] = set()
|
|
352
|
+
for node in scoped_nodes:
|
|
353
|
+
if not isinstance(node, ast.Call):
|
|
354
|
+
continue
|
|
355
|
+
if isinstance(node.func, ast.Attribute) and node.func.attr == _MODEL_VALIDATE:
|
|
356
|
+
if len(node.args) != 1 or node.keywords or _fetch_method(node.args[0], cursor) != "fetchone":
|
|
357
|
+
continue
|
|
358
|
+
model = _model_name(node.func.value)
|
|
359
|
+
else:
|
|
360
|
+
direct_rows = [
|
|
361
|
+
keyword.value
|
|
362
|
+
for keyword in node.keywords
|
|
363
|
+
if keyword.arg is None and _fetch_method(keyword.value, cursor) == "fetchone"
|
|
364
|
+
]
|
|
365
|
+
model = _model_name(node.func) if len(direct_rows) == 1 else None
|
|
366
|
+
if model is not None:
|
|
367
|
+
models.add(model)
|
|
368
|
+
return models
|
|
369
|
+
|
|
370
|
+
|
|
311
371
|
def _fetched_values(scoped_nodes: list[ast.AST], cursor: str) -> list[_FetchedValue]:
|
|
312
372
|
fetched: list[_FetchedValue] = []
|
|
313
373
|
for node in scoped_nodes:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/_analysis_session.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.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/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
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_dunder_all.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
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/no_select_star.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
|
{sarj_python_lint-0.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/over_mocked_test.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.81.4 → sarj_python_lint-0.81.5}/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.81.4 → sarj_python_lint-0.81.5}/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.81.4 → sarj_python_lint-0.81.5}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|