sarj-python-lint 1.46.2__tar.gz → 1.47.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.
Files changed (217) hide show
  1. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/pyproject.toml +1 -1
  3. sarj_python_lint-1.47.0/src/sarj_python_lint/rules/_data_union_discriminator.py +404 -0
  4. sarj_python_lint-1.47.0/src/sarj_python_lint/rules/no_unnecessary_docstring.py +1985 -0
  5. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +51 -8
  6. sarj_python_lint-1.46.2/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -802
  7. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/.gitignore +0 -0
  8. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/LICENSE +0 -0
  9. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/README.md +0 -0
  10. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/__init__.py +0 -0
  11. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/__main__.py +0 -0
  12. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  13. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/_file_context.py +0 -0
  14. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/_filesystem.py +0 -0
  15. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/_python_target.py +0 -0
  16. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  17. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/_secret_names.py +0 -0
  18. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/_source.py +0 -0
  19. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/_version.py +0 -0
  20. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/benchmark.py +0 -0
  21. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  22. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/json_boundary.py +0 -0
  23. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/py.typed +0 -0
  24. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/ratchet.py +0 -0
  25. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rule_base.py +0 -0
  26. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  27. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  28. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  29. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  30. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  31. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  32. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  33. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  34. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  35. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  36. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  37. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  38. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  39. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  40. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  41. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  42. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  43. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  44. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  45. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  46. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  47. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  48. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  49. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  50. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  51. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  52. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  53. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  54. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  55. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  56. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  57. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  58. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  59. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  60. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  61. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  62. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  63. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  64. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  65. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  66. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  67. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  68. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  69. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  70. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  71. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  72. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  73. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  74. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  75. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  76. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  77. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  78. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  79. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  80. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  81. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  82. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  83. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  84. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  85. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  86. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  87. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  88. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  89. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  90. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  91. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  92. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  93. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  94. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  95. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  96. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  97. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  98. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  99. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  100. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  101. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  102. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  103. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  104. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  105. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  106. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  107. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  108. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  109. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  110. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  111. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  112. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  113. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  114. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  115. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  116. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  117. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  118. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  119. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  120. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  121. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  122. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  123. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  124. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  125. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  126. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  127. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  128. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  129. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  130. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  131. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  132. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  133. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  134. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  135. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  136. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  137. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  138. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  139. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  140. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  141. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  142. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  143. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  144. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  145. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  146. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  147. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  148. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  149. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  150. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  151. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  152. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  153. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  154. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  155. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  156. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  157. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  158. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  159. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  160. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  161. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  162. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  163. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  164. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  165. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  166. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  167. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  168. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  169. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  170. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  171. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  172. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  173. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  174. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  175. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  176. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  177. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  178. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  179. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  180. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  181. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  182. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  183. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  184. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  185. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  186. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  187. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  188. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  189. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  190. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  191. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  192. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  193. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  194. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  195. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  196. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  197. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  198. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  199. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  200. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  201. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  202. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  203. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  204. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  205. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  206. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  207. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  208. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  209. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  210. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  211. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  212. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  213. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  214. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  215. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  216. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  217. {sarj_python_lint-1.46.2 → sarj_python_lint-1.47.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.46.2
3
+ Version: 1.47.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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "1.46.2"
3
+ version = "1.47.0"
4
4
  description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "sarj-ai" }]
@@ -0,0 +1,404 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, final
6
+
7
+ from sarj_python_lint.rule_base import Diagnostic, Severity, is_suppressed
8
+ from sarj_python_lint.rules._imports import TYPING_SOURCES
9
+ from sarj_python_lint.rules._pydantic_field_contract import MODEL_SOURCES, unique_members
10
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from sarj_python_lint._file_context import PythonFileContext
15
+
16
+
17
+ _DATACLASS_SOURCES = frozenset({"dataclasses"})
18
+ _MAX_EXPANSION = 128
19
+ _MIN_VARIANTS = 2
20
+
21
+ type _Tag = str | bytes | int | None
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class _Record:
26
+ fields: dict[str, ast.AnnAssign]
27
+ required: bool
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class _Domain:
32
+ values: frozenset[_Tag] = frozenset()
33
+ opaque: bool = False
34
+ unresolved: bool = False
35
+
36
+
37
+ @dataclass(frozen=True, slots=True)
38
+ class _Expansion:
39
+ node: ast.expr
40
+ aliases: frozenset[str] = frozenset()
41
+
42
+
43
+ @dataclass(slots=True)
44
+ class _Budget:
45
+ remaining: int = _MAX_EXPANSION
46
+
47
+
48
+ def data_union_diagnostics(context: PythonFileContext, code: str) -> list[Diagnostic]:
49
+ if not any(symbol in context.symbol_source for symbol in ("dataclass", "BaseModel", "TypedDict")):
50
+ return []
51
+ tree = context.tree
52
+ if tree is None or context.generated or context.has_wildcard_import:
53
+ return []
54
+ if not any(
55
+ value is not None and _union_expression(value, context)
56
+ for node in tree.body
57
+ for value in (_alias_value(node, context),)
58
+ ):
59
+ return []
60
+ analysis = _DataUnions(context, tree)
61
+ if analysis.provenance.dynamic_namespace:
62
+ return []
63
+ return analysis.diagnostics(code)
64
+
65
+
66
+ @final
67
+ class _DataUnions:
68
+ def __init__(self, context: PythonFileContext, tree: ast.Module) -> None:
69
+ self.context = context
70
+ self.provenance = ResourceProvenance(context, require_static_namespace=True)
71
+ bindings = self.provenance.bindings.get(tree, {})
72
+ self.classes = {
73
+ node.name: node
74
+ for node in tree.body
75
+ if isinstance(node, ast.ClassDef)
76
+ and len(bindings.get(node.name, ())) == 1
77
+ and node.name not in self.provenance.mutated
78
+ }
79
+ self.aliases: dict[str, ast.expr] = {}
80
+ self.declarations: dict[str, ast.stmt] = {}
81
+ for node in tree.body:
82
+ value = _alias_value(node, context)
83
+ name = _alias_name(node)
84
+ if value is not None and name is not None and len(bindings.get(name, ())) == 1:
85
+ self.aliases[name] = value
86
+ self.declarations[name] = node
87
+ self.records: dict[str, _Record | None] = {}
88
+
89
+ def diagnostics(self, code: str) -> list[Diagnostic]:
90
+ findings: list[Diagnostic] = []
91
+ for name, value in self.aliases.items():
92
+ node = self.declarations[name]
93
+ if is_suppressed(self.context.source_lines, node.lineno, code) or not self._untagged_union(value):
94
+ continue
95
+ findings.append(
96
+ Diagnostic(
97
+ path=self.context.path,
98
+ line=node.lineno,
99
+ col=node.col_offset + 1,
100
+ code=code,
101
+ message=f"Data union `{name}` has no shared disjoint Literal discriminator. "
102
+ "Add a common field such as `kind` or `scope` with non-overlapping Literal values "
103
+ "on every variant; preserve constructor and serialized contracts when choosing defaults.",
104
+ severity=Severity.WARNING,
105
+ )
106
+ )
107
+ return findings
108
+
109
+ def _untagged_union(self, value: ast.expr) -> bool:
110
+ if not _union_expression(value, self.context):
111
+ return False
112
+ variants = self._variants(value)
113
+ if variants is None or len(variants) < _MIN_VARIANTS:
114
+ return False
115
+ records: list[_Record] = []
116
+ for variant in sorted(variants):
117
+ record = self._record(variant)
118
+ if record is None:
119
+ return False
120
+ records.append(record)
121
+ return not self._has_discriminator(records, _Budget())
122
+
123
+ def _typing_symbol(self, node: ast.expr) -> str | None:
124
+ if not _untouched(node, self.provenance):
125
+ return None
126
+ return self.context.imports.resolved_symbol(node, sources=TYPING_SOURCES)
127
+
128
+ def _variants(self, expression: ast.expr) -> set[str] | None:
129
+ leaves = self._leaves(expression)
130
+ if leaves is None:
131
+ return None
132
+ variants: set[str] = set()
133
+ for node in leaves:
134
+ if isinstance(node, ast.Constant) and node.value is None:
135
+ continue
136
+ if not isinstance(node, ast.Name) or node.id not in self.classes:
137
+ return None
138
+ variants.add(node.id)
139
+ return variants
140
+
141
+ def _leaves(self, expression: ast.expr) -> list[ast.expr] | None:
142
+ pending = [_Expansion(expression)]
143
+ leaves: list[ast.expr] = []
144
+ for _ in range(_MAX_EXPANSION):
145
+ if not pending:
146
+ return leaves
147
+ item = pending.pop()
148
+ node = _string_expression(item.node)
149
+ if node is None:
150
+ return None
151
+ if isinstance(node, ast.Name) and node.id in self.aliases:
152
+ if node.id in item.aliases:
153
+ return None
154
+ pending.append(_Expansion(self.aliases[node.id], item.aliases | {node.id}))
155
+ else:
156
+ children = self._union_members(node)
157
+ if children is None:
158
+ leaves.append(node)
159
+ else:
160
+ pending.extend(_Expansion(child, item.aliases) for child in children)
161
+ return None
162
+
163
+ def _union_members(self, node: ast.expr) -> tuple[ast.expr, ...] | None:
164
+ if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
165
+ return node.left, node.right
166
+ if not isinstance(node, ast.Subscript):
167
+ return None
168
+ symbol = self._typing_symbol(node.value)
169
+ if symbol == "Union" and isinstance(node.slice, ast.Tuple):
170
+ return tuple(node.slice.elts)
171
+ if symbol == "Optional":
172
+ return node.slice, ast.Constant(value=None)
173
+ if symbol == "Annotated" and isinstance(node.slice, ast.Tuple) and node.slice.elts:
174
+ return (node.slice.elts[0],)
175
+ return None
176
+
177
+ def _record(self, name: str) -> _Record | None:
178
+ if name not in self.records:
179
+ self.records[name] = _record(self.classes[name], self.context, self.provenance)
180
+ return self.records[name]
181
+
182
+ def _has_discriminator(self, records: list[_Record], budget: _Budget) -> bool:
183
+ if budget.remaining <= 0:
184
+ return True
185
+ budget.remaining -= 1
186
+ common = set(records[0].fields).intersection(*(record.fields for record in records[1:]))
187
+ common.difference_update({name for name in common if name.startswith("__")})
188
+ uncertain = False
189
+ for name in sorted(common):
190
+ domains = [self._field_domain(record.fields[name], required=record.required) for record in records]
191
+ uncertain |= _uncertain_domains(domains)
192
+ groups = _tag_groups(records, domains)
193
+ if groups and all(len(group) == 1 or self._has_discriminator(group, budget) for group in groups):
194
+ return True
195
+ return uncertain
196
+
197
+ def _field_domain(self, field: ast.AnnAssign, *, required: bool) -> _Domain:
198
+ node = field.annotation
199
+ for _ in range(_MAX_EXPANSION):
200
+ parsed = _string_expression(node)
201
+ if parsed is None:
202
+ return _Domain(opaque=True)
203
+ node = parsed
204
+ if not isinstance(node, ast.Subscript):
205
+ break
206
+ symbol = self._typing_symbol(node.value)
207
+ if symbol in {"Required", "NotRequired"}:
208
+ required = symbol == "Required"
209
+ node = node.slice
210
+ elif symbol == "ClassVar":
211
+ if field.value is None:
212
+ return _Domain()
213
+ node = node.slice
214
+ elif symbol == "ReadOnly":
215
+ node = node.slice
216
+ elif symbol == "Annotated" and isinstance(node.slice, ast.Tuple) and node.slice.elts:
217
+ node = node.slice.elts[0]
218
+ else:
219
+ break
220
+ return self._literal_domain(node) if required else _Domain()
221
+
222
+ def _literal_domain(self, expression: ast.expr) -> _Domain:
223
+ leaves = self._leaves(expression)
224
+ if leaves is None:
225
+ return _Domain(opaque=True)
226
+ domains = [self._leaf_domain(node) for node in leaves]
227
+ if any(domain.opaque for domain in domains):
228
+ return _Domain(opaque=True)
229
+ if any(domain.unresolved for domain in domains):
230
+ return _Domain(unresolved=True)
231
+ if any(not domain.values for domain in domains):
232
+ return _Domain()
233
+ return _Domain(frozenset(value for domain in domains for value in domain.values))
234
+
235
+ def _leaf_domain(self, node: ast.expr) -> _Domain:
236
+ if isinstance(node, ast.Constant) and node.value is None:
237
+ return _Domain(frozenset({None}))
238
+ if isinstance(node, ast.Name) and (
239
+ node.id in self.classes or self.context.imports.resolved_qualified_name(node) is not None
240
+ ):
241
+ return _Domain(unresolved=True)
242
+ if not isinstance(node, ast.Subscript):
243
+ return _Domain()
244
+ symbol = self._typing_symbol(node.value)
245
+ if symbol == "Literal":
246
+ return _literal_values(node)
247
+ if symbol is not None or (
248
+ isinstance(node.value, ast.Name)
249
+ and node.value.id in {"list", "dict", "tuple", "set", "frozenset", "type"}
250
+ and self.context.imports.builtin_is_unshadowed(node.value.id)
251
+ ):
252
+ return _Domain()
253
+ return _Domain(opaque=True)
254
+
255
+
256
+ def _record(node: ast.ClassDef, context: PythonFileContext, provenance: ResourceProvenance) -> _Record | None:
257
+ required = _record_required(node, context, provenance)
258
+ if required is None:
259
+ return None
260
+ fields: dict[str, ast.AnnAssign] = {}
261
+ unique = unique_members(node)
262
+ for member in node.body:
263
+ if isinstance(member, ast.AnnAssign) and isinstance(member.target, ast.Name):
264
+ if member.target.id not in unique:
265
+ return None
266
+ fields[member.target.id] = member
267
+ elif isinstance(member, ast.Pass | ast.Expr) and (
268
+ isinstance(member, ast.Pass)
269
+ or (isinstance(member.value, ast.Constant) and isinstance(member.value.value, str))
270
+ ):
271
+ continue
272
+ else:
273
+ return None
274
+ return _Record(fields, required)
275
+
276
+
277
+ def _record_required(node: ast.ClassDef, context: PythonFileContext, provenance: ResourceProvenance) -> bool | None:
278
+ if node.type_params:
279
+ return None
280
+ if any(not _untouched(base, provenance) for base in node.bases):
281
+ return None
282
+ if node.decorator_list:
283
+ return _direct_dataclass(node, context, provenance)
284
+ if len(node.bases) != 1:
285
+ return None
286
+ if context.imports.resolves(node.bases[0], sources=MODEL_SOURCES, symbol="BaseModel"):
287
+ return True if not node.keywords else None
288
+ if not context.imports.resolves(node.bases[0], sources=TYPING_SOURCES, symbol="TypedDict"):
289
+ return None
290
+ required = True
291
+ for keyword in node.keywords:
292
+ if keyword.arg != "total" or not isinstance(keyword.value, ast.Constant):
293
+ return None
294
+ if not isinstance(keyword.value.value, bool):
295
+ return None
296
+ required = keyword.value.value
297
+ return required
298
+
299
+
300
+ def _direct_dataclass(node: ast.ClassDef, context: PythonFileContext, provenance: ResourceProvenance) -> bool | None:
301
+ if node.bases or node.keywords or len(node.decorator_list) != 1:
302
+ return None
303
+ decorator = node.decorator_list[0]
304
+ target = decorator.func if isinstance(decorator, ast.Call) else decorator
305
+ if not _untouched(target, provenance) or not context.imports.resolves(
306
+ target, sources=_DATACLASS_SOURCES, symbol="dataclass"
307
+ ):
308
+ return None
309
+ return True
310
+
311
+
312
+ def _literal_values(node: ast.Subscript) -> _Domain:
313
+ values: set[_Tag] = set()
314
+ members = node.slice.elts if isinstance(node.slice, ast.Tuple) else [node.slice]
315
+ for member in members:
316
+ match member:
317
+ case ast.Constant(value=(str() | bytes() | int()) as value):
318
+ values.add(value)
319
+ case ast.Constant(value=None):
320
+ values.add(None)
321
+ case ast.UnaryOp(op=op, operand=ast.Constant(value=int() as number)) if (
322
+ isinstance(op, ast.USub | ast.UAdd) and type(number) is int
323
+ ):
324
+ values.add(-number if isinstance(op, ast.USub) else number)
325
+ case _:
326
+ return _Domain(opaque=True)
327
+ return _Domain(frozenset(values))
328
+
329
+
330
+ def _tag_groups(records: list[_Record], domains: list[_Domain]) -> list[list[_Record]]:
331
+ if any(not domain.values or domain.opaque for domain in domains):
332
+ return []
333
+ groups: dict[_Tag, list[_Record]] = {}
334
+ for record, domain in zip(records, domains, strict=True):
335
+ for value in sorted(domain.values, key=repr):
336
+ groups.setdefault(value, []).append(record)
337
+ if any(len(group) == len(records) for group in groups.values()):
338
+ return []
339
+ return list(groups.values())
340
+
341
+
342
+ def _uncertain_domains(domains: list[_Domain]) -> bool:
343
+ return any(domain.opaque for domain in domains) or (
344
+ any(domain.values for domain in domains) and any(domain.unresolved for domain in domains)
345
+ )
346
+
347
+
348
+ def _alias_name(node: ast.stmt) -> str | None:
349
+ match node:
350
+ case ast.TypeAlias(name=ast.Name(id=name)) | ast.AnnAssign(target=ast.Name(id=name)):
351
+ return name
352
+ case ast.Assign(targets=[ast.Name(id=name)]):
353
+ return name
354
+ case _:
355
+ return None
356
+
357
+
358
+ def _alias_value(node: ast.stmt, context: PythonFileContext) -> ast.expr | None:
359
+ match node:
360
+ case ast.TypeAlias(type_params=[]):
361
+ return node.value
362
+ case ast.AnnAssign(target=ast.Name(), annotation=annotation, value=value):
363
+ return value if context.imports.resolves(annotation, sources=TYPING_SOURCES, symbol="TypeAlias") else None
364
+ case ast.Assign(targets=[ast.Name()], value=value):
365
+ return None if isinstance(value, ast.Constant) and isinstance(value.value, str) else value
366
+ case _:
367
+ return None
368
+
369
+
370
+ def _string_expression(node: ast.expr) -> ast.expr | None:
371
+ if not isinstance(node, ast.Constant) or not isinstance(node.value, str):
372
+ return node
373
+ try:
374
+ return ast.parse(node.value, mode="eval").body
375
+ except SyntaxError, UnicodeError, RecursionError:
376
+ return None
377
+
378
+
379
+ def _untouched(node: ast.expr, provenance: ResourceProvenance) -> bool:
380
+ while isinstance(node, ast.Attribute):
381
+ node = node.value
382
+ return isinstance(node, ast.Name) and node.id not in provenance.mutated
383
+
384
+
385
+ def _union_expression(node: ast.expr, context: PythonFileContext) -> bool:
386
+ for _ in range(_MAX_EXPANSION):
387
+ parsed = _string_expression(node)
388
+ if parsed is None:
389
+ return False
390
+ node = parsed
391
+ if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
392
+ return True
393
+ if not isinstance(node, ast.Subscript):
394
+ return False
395
+ symbol = context.imports.resolved_symbol(node.value, sources=TYPING_SOURCES)
396
+ if symbol == "Union":
397
+ return True
398
+ if symbol == "Optional":
399
+ node = node.slice
400
+ elif symbol == "Annotated" and isinstance(node.slice, ast.Tuple) and node.slice.elts:
401
+ node = node.slice.elts[0]
402
+ else:
403
+ return False
404
+ return False