plain.postgres 0.98.0__tar.gz → 0.99.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 (206) hide show
  1. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/PKG-INFO +101 -20
  2. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/CHANGELOG.md +15 -0
  3. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/README.md +100 -19
  4. plain_postgres-0.99.0/plain/postgres/cli/diagnose.py +312 -0
  5. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/introspection/__init__.py +2 -0
  6. plain_postgres-0.99.0/plain/postgres/introspection/health/__init__.py +34 -0
  7. plain_postgres-0.99.0/plain/postgres/introspection/health/checks_cumulative.py +670 -0
  8. plain_postgres-0.99.0/plain/postgres/introspection/health/checks_snapshot.py +330 -0
  9. plain_postgres-0.99.0/plain/postgres/introspection/health/checks_structural.py +292 -0
  10. plain_postgres-0.99.0/plain/postgres/introspection/health/context.py +223 -0
  11. plain_postgres-0.99.0/plain/postgres/introspection/health/helpers.py +164 -0
  12. plain_postgres-0.99.0/plain/postgres/introspection/health/ownership.py +65 -0
  13. plain_postgres-0.99.0/plain/postgres/introspection/health/runner.py +158 -0
  14. plain_postgres-0.99.0/plain/postgres/introspection/health/types.py +54 -0
  15. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/pyproject.toml +1 -1
  16. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_connection_lifecycle.py +2 -2
  17. plain_postgres-0.99.0/tests/test_diagnose.py +244 -0
  18. plain_postgres-0.99.0/tests/test_health.py +47 -0
  19. plain_postgres-0.98.0/plain/postgres/cli/diagnose.py +0 -206
  20. plain_postgres-0.98.0/plain/postgres/introspection/health.py +0 -737
  21. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/.gitignore +0 -0
  22. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/CLAUDE.md +0 -0
  23. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/LICENSE +0 -0
  24. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/README.md +0 -0
  25. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/__init__.py +0 -0
  26. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/adapters.py +0 -0
  27. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/agents/.claude/rules/plain-postgres.md +0 -0
  28. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/agents/.claude/skills/plain-postgres-doctor/SKILL.md +0 -0
  29. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/aggregates.py +0 -0
  30. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/base.py +0 -0
  31. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/cli/__init__.py +0 -0
  32. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/cli/converge.py +0 -0
  33. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/cli/core.py +0 -0
  34. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/cli/decorators.py +0 -0
  35. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/cli/migrations.py +0 -0
  36. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/cli/schema.py +0 -0
  37. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/cli/sync.py +0 -0
  38. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/config.py +0 -0
  39. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/connection.py +0 -0
  40. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/constants.py +0 -0
  41. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/constraints.py +0 -0
  42. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/convergence/__init__.py +0 -0
  43. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/convergence/analysis.py +0 -0
  44. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/convergence/fixes.py +0 -0
  45. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/convergence/planning.py +0 -0
  46. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/database_url.py +0 -0
  47. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/db.py +0 -0
  48. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/ddl.py +0 -0
  49. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/default_settings.py +0 -0
  50. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/deletion.py +0 -0
  51. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/dialect.py +0 -0
  52. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/entrypoints.py +0 -0
  53. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/enums.py +0 -0
  54. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/exceptions.py +0 -0
  55. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/expressions.py +0 -0
  56. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/__init__.py +0 -0
  57. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/base.py +0 -0
  58. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/binary.py +0 -0
  59. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/boolean.py +0 -0
  60. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/duration.py +0 -0
  61. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/encrypted.py +0 -0
  62. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/json.py +0 -0
  63. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/mixins.py +0 -0
  64. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/network.py +0 -0
  65. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/numeric.py +0 -0
  66. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/primary_key.py +0 -0
  67. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/related.py +0 -0
  68. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/related_descriptors.py +0 -0
  69. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/related_lookups.py +0 -0
  70. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/related_managers.py +0 -0
  71. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/reverse_descriptors.py +0 -0
  72. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/reverse_related.py +0 -0
  73. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/temporal.py +0 -0
  74. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/text.py +0 -0
  75. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/timezones.py +0 -0
  76. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/fields/uuid.py +0 -0
  77. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/forms.py +0 -0
  78. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/__init__.py +0 -0
  79. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/comparison.py +0 -0
  80. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/datetime.py +0 -0
  81. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/math.py +0 -0
  82. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/mixins.py +0 -0
  83. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/random.py +0 -0
  84. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/text.py +0 -0
  85. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/uuid.py +0 -0
  86. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/functions/window.py +0 -0
  87. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/indexes.py +0 -0
  88. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/introspection/schema.py +0 -0
  89. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/lookups.py +0 -0
  90. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/meta.py +0 -0
  91. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/middleware.py +0 -0
  92. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/__init__.py +0 -0
  93. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/autodetector.py +0 -0
  94. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/exceptions.py +0 -0
  95. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/executor.py +0 -0
  96. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/graph.py +0 -0
  97. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/loader.py +0 -0
  98. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/migration.py +0 -0
  99. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/operations/__init__.py +0 -0
  100. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/operations/base.py +0 -0
  101. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/operations/fields.py +0 -0
  102. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/operations/models.py +0 -0
  103. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/operations/special.py +0 -0
  104. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/optimizer.py +0 -0
  105. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/questioner.py +0 -0
  106. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/recorder.py +0 -0
  107. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/serializer.py +0 -0
  108. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/state.py +0 -0
  109. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/utils.py +0 -0
  110. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/migrations/writer.py +0 -0
  111. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/options.py +0 -0
  112. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/otel.py +0 -0
  113. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/preflight.py +0 -0
  114. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/query.py +0 -0
  115. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/query_utils.py +0 -0
  116. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/registry.py +0 -0
  117. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/schema.py +0 -0
  118. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/sources.py +0 -0
  119. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/sql/__init__.py +0 -0
  120. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/sql/compiler.py +0 -0
  121. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/sql/constants.py +0 -0
  122. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/sql/datastructures.py +0 -0
  123. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/sql/query.py +0 -0
  124. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/sql/where.py +0 -0
  125. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/test/__init__.py +0 -0
  126. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/test/database.py +0 -0
  127. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/test/pytest.py +0 -0
  128. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/transaction.py +0 -0
  129. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/types.py +0 -0
  130. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/types.pyi +0 -0
  131. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/plain/postgres/utils.py +0 -0
  132. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/forms.py +0 -0
  133. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0001_initial.py +0 -0
  134. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
  135. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
  136. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0004_defaultquerysetmodel_mixintestmodel_and_more.py +0 -0
  137. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0005_feature_carfeature_car_features.py +0 -0
  138. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0006_secretstore.py +0 -0
  139. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0007_treenode_unconstrainedchild.py +0 -0
  140. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0008_setsentinelparent_diamondparenta_midparent_and_more.py +0 -0
  141. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0009_circb_circa_circb_partner.py +0 -0
  142. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0010_hideableitem.py +0 -0
  143. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0011_defaultsexample.py +0 -0
  144. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0012_iterationexample.py +0 -0
  145. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0013_indexexample_constraintexample_nullabilityexample.py +0 -0
  146. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0014_widget_rename_feature_tag_remove_carfeature_car_and_more.py +0 -0
  147. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0015_dbdefaultsexample.py +0 -0
  148. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0016_formsexample.py +0 -0
  149. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/0017_random_string_token.py +0 -0
  150. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/migrations/__init__.py +0 -0
  151. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/__init__.py +0 -0
  152. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/constraints.py +0 -0
  153. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/defaults.py +0 -0
  154. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/delete.py +0 -0
  155. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/encrypted.py +0 -0
  156. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/forms.py +0 -0
  157. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/indexes.py +0 -0
  158. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/iteration.py +0 -0
  159. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/mixins.py +0 -0
  160. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/nullability.py +0 -0
  161. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/querysets.py +0 -0
  162. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/relationships.py +0 -0
  163. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/trees.py +0 -0
  164. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/models/unregistered.py +0 -0
  165. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/urls.py +0 -0
  166. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/examples/views.py +0 -0
  167. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/settings.py +0 -0
  168. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/app/urls.py +0 -0
  169. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/conftest.py +0 -0
  170. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/conftest_convergence.py +0 -0
  171. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_autodetector_not_null_errors.py +0 -0
  172. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_autodetector_type_change.py +0 -0
  173. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_connection_isolation.py +0 -0
  174. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_connection_pool.py +0 -0
  175. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_convergence.py +0 -0
  176. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_convergence_constraints.py +0 -0
  177. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_convergence_defaults.py +0 -0
  178. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_convergence_fk.py +0 -0
  179. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_convergence_indexes.py +0 -0
  180. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_convergence_nullability.py +0 -0
  181. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_convergence_timeouts.py +0 -0
  182. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_database_url.py +0 -0
  183. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_db_expression_defaults.py +0 -0
  184. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_delete_behaviors.py +0 -0
  185. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_encrypted_fields.py +0 -0
  186. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_exceptions.py +0 -0
  187. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_executor_connection_hook.py +0 -0
  188. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_field_defaults.py +0 -0
  189. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_functions_uuid.py +0 -0
  190. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_introspection.py +0 -0
  191. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_iterator.py +0 -0
  192. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_literal_default_persistence.py +0 -0
  193. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_m2m.py +0 -0
  194. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_management_connection.py +0 -0
  195. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_manager_assignment.py +0 -0
  196. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_migration_executor.py +0 -0
  197. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_mixins.py +0 -0
  198. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_modelform_roundtrip.py +0 -0
  199. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_no_callable_defaults.py +0 -0
  200. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_otel_metrics.py +0 -0
  201. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_random_string_field.py +0 -0
  202. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_raw_query.py +0 -0
  203. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_read_only_transactions.py +0 -0
  204. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_related.py +0 -0
  205. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_schema_normalize_type.py +0 -0
  206. {plain_postgres-0.98.0 → plain_postgres-0.99.0}/tests/test_schema_timeouts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.postgres
3
- Version: 0.98.0
3
+ Version: 0.99.0
4
4
  Summary: Model your data and store it in a database.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-Expression: BSD-3-Clause
@@ -1198,37 +1198,110 @@ graph TB
1198
1198
 
1199
1199
  ## Diagnostics
1200
1200
 
1201
- You can run health checks against your database to find issues like missing indexes, redundant indexes, and configuration problems.
1201
+ Run health checks against your database. `diagnose` is designed to produce
1202
+ only actionable findings — every warning has a copy-paste fix or specific
1203
+ resource to investigate, and noisy one-off signals (hit ratios, XID age) are
1204
+ surfaced as informational context rather than as warnings.
1202
1205
 
1203
1206
  ```bash
1204
1207
  uv run plain postgres diagnose
1205
1208
  ```
1206
1209
 
1207
- Use `--json` for structured output (useful for scripting and AI agents):
1210
+ Output modes:
1208
1211
 
1209
1212
  ```bash
1210
- uv run plain postgres diagnose --json
1213
+ uv run plain postgres diagnose --json # structured output for scripts/agents
1214
+ uv run plain postgres diagnose --verbose # expand to show every check, including passing
1215
+ uv run plain postgres diagnose --all # include findings on installed-package tables
1211
1216
  ```
1212
1217
 
1213
- Use `--all` to include issues in installed packages (by default, only your app's issues are shown):
1218
+ ### Guiding principle
1214
1219
 
1215
- ```bash
1216
- uv run plain postgres diagnose --all
1217
- ```
1220
+ `diagnose` emits a **warning** only if the remedy fits in the user's codebase
1221
+ or is an app-level action they own. If the remedy is "run SQL against your
1222
+ DB" or "configure your Postgres server," the check emits **operational
1223
+ context** or an **informational number**, not a warning. This keeps the
1224
+ warning surface high-trust — every warning has an edit-to-make — and prevents
1225
+ `diagnose` from bleeding into DB-host concerns.
1226
+
1227
+ ### Warning-tier checks
1228
+
1229
+ Things the user can fix by editing code + running `plain postgres sync`, or
1230
+ app-level incidents they must act on.
1231
+
1232
+ **Structural — always-real; a fix is possible immediately.**
1233
+
1234
+ | Check | What it finds | Severity |
1235
+ | ----------------------- | --------------------------------------------------------------------------------------------------- | ---------------- |
1236
+ | **Invalid indexes** | Broken indexes from failed `CREATE INDEX CONCURRENTLY` — maintained on writes, never used for reads | Warning |
1237
+ | **Duplicate indexes** | One index is a column-prefix of another on the same table | Warning |
1238
+ | **Missing FK indexes** | Foreign key columns without any index coverage | Warning |
1239
+ | **Sequence exhaustion** | Identity sequences approaching their type max | Warning/Critical |
1240
+
1241
+ **Cumulative — depends on stats since the last reset.**
1242
+
1243
+ | Check | What it finds | Severity |
1244
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- |
1245
+ | **Unused indexes** | Indexes with zero scans since stats reset (>1 MB). Excludes unique, constraint-backing, and sole-FK-coverage indexes | Warning |
1246
+ | **Missing index candidates** | Tables with seq-scan activity suggesting a missing index. Includes top contributing queries from pg_stat_statements | Warning |
1247
+
1248
+ **Snapshot — point-in-time incidents.**
1249
+
1250
+ | Check | What it finds | Severity |
1251
+ | ---------------------------- | ----------------------------------------------------------------------- | ---------------- |
1252
+ | **Long-running connections** | Client backends idle-in-transaction or running a query past a threshold | Warning/Critical |
1253
+ | **Blocking queries** | Queries currently blocking other queries via held locks | Warning/Critical |
1254
+
1255
+ ### Operational-context findings
1256
+
1257
+ These are facts about the database whose remedies live outside Plain today
1258
+ (`ANALYZE`, `VACUUM`, `REINDEX`, autovacuum server tuning). They're surfaced
1259
+ so agents and humans can interpret findings correctly, but the CLI renders
1260
+ them as context rather than alarming warnings — the user can't express the
1261
+ fix in their model code. (In JSON output each finding still carries
1262
+ `status: "warning"`; the `tier: "operational"` field is what distinguishes
1263
+ it.) Each finding still carries the exact SQL in its suggestion for anyone
1264
+ who wants to act.
1265
+
1266
+ | Finding | What it reports |
1267
+ | ------------------- | -------------------------------------------------------------------------------- |
1268
+ | **Stats freshness** | Tables whose planner statistics are missing (never analyzed) or stale |
1269
+ | **Vacuum health** | Tables with >10% dead tuples |
1270
+ | **Index bloat** | btree indexes with significant estimated wasted space (≥10 MB, ioguix estimator) |
1271
+
1272
+ If a future release exposes per-table autovacuum / fillfactor parameters in
1273
+ `model_options` (see the `postgres-model-storage-parameters` arc), these
1274
+ findings can graduate back to the warning tier — because the remedy will be
1275
+ expressible in code.
1218
1276
 
1219
- ### Checks
1277
+ ### Informational context
1220
1278
 
1221
- | Check | What it finds | Severity |
1222
- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
1223
- | **Invalid indexes** | Broken indexes from failed `CREATE INDEX CONCURRENTLY` maintained on writes, never used for reads | Warning |
1224
- | **Duplicate indexes** | Indexes where one is a column-prefix of another on the same table (e.g., an auto FK index that's redundant with a composite index) | Warning |
1225
- | **Unused indexes** | Indexes with zero scans since stats reset (>1 MB). Excludes unique indexes, constraint-backing indexes, and indexes that are the sole coverage for a FK column | Warning |
1226
- | **Missing FK indexes** | Foreign key columns without any index coverage parent DELETE/UPDATE operations will sequentially scan the child table | Warning |
1227
- | **Sequence exhaustion** | Identity sequences approaching their type max (>50% warning, >90% critical) | Warning/Critical |
1228
- | **XID wraparound** | Transaction ID age approaching the 2 billion wraparound limit (>25% warning, >40% critical) | Warning/Critical |
1229
- | **Cache hit ratio** | Heap buffer hit ratio below 98.5% — indicates insufficient `shared_buffers` or RAM | Warning |
1230
- | **Index hit ratio** | Index buffer hit ratio below 98.5% | Warning |
1231
- | **Vacuum health** | Tables with significant dead tuple accumulation (>10% of live rows) where autovacuum may be falling behind | Warning |
1279
+ Alongside checks, `diagnose` surfaces context an agent or human may want to read but that isn't actionable on its own:
1280
+
1281
+ - **Cache hit ratio**, **Index hit ratio** buffer hit rates (volatile after restart; not a warning in themselves)
1282
+ - **XID wraparound** transaction ID age as a percent of the 2B limit. Autovacuum usually keeps this low; long-running transactions can block the freeze process even on managed Postgres
1283
+ - **Connection saturation** active/max connections at this moment
1284
+ - **Stats reset** when cumulative stats were last reset (affects the confidence of operational checks)
1285
+ - **pg_stat_statements** whether the extension is installed
1286
+
1287
+ ### Cross-check caveats
1288
+
1289
+ Findings whose confidence depends on another check are tagged with a caveat. For example:
1290
+
1291
+ - `unused_indexes` on a table flagged by `stats_freshness` → caveat: "this table has never been analyzed — the planner may not yet use this index; re-check after running ANALYZE"
1292
+ - `missing_index_candidates` on a never-analyzed table → caveat: "planner statistics are absent — running ANALYZE may change query plans and make this finding moot"
1293
+
1294
+ This prevents false confidence: dropping an "unused" index on a never-analyzed table is often the wrong move.
1295
+
1296
+ ### Model-aware suggestions
1297
+
1298
+ Findings on app-owned tables include the Plain model class and its source file. Suggestions reference the exact edit point:
1299
+
1300
+ ```
1301
+ app/processing/models.py :: ProcessingResult — Add an Index on ["is_processing"] to the model, then run plain postgres sync
1302
+ ```
1303
+
1304
+ This closes the loop from detection to fix — agents can draft the model edit without guessing.
1232
1305
 
1233
1306
  ### App vs package issues
1234
1307
 
@@ -1248,6 +1321,8 @@ heroku run -a your-app "plain postgres diagnose --json"
1248
1321
 
1249
1322
  The `--json` flag must be quoted so Heroku passes it through to the command.
1250
1323
 
1324
+ Cumulative-stat checks (`stats_freshness`, `vacuum_health`, `unused_indexes`, `missing_index_candidates`, `index_bloat`) need cumulative stat history after the last reset to be reliable. Check the `stats_reset` informational to see how much history you have. (Note: this list spans both the warning and operational tiers — the common thread is that all five depend on counters that `pg_stat_reset()` wipes.)
1325
+
1251
1326
  ### Preflight checks
1252
1327
 
1253
1328
  Two related checks run automatically during `uv run plain preflight` (and `uv run plain check`):
@@ -1257,6 +1332,12 @@ Two related checks run automatically during `uv run plain preflight` (and `uv ru
1257
1332
 
1258
1333
  These are static, code-level checks that catch issues before you deploy. The `diagnose` command complements them with runtime stats from the actual database.
1259
1334
 
1335
+ ### What diagnose deliberately doesn't do
1336
+
1337
+ - **LLM-powered column recommendations for missing indexes** — `missing_index_candidates` shows the culprit queries and lets you decide. For precise column-level suggestions, use a platform tool (PlanetScale Insights, Dexter, pg_qualstats + hypopg).
1338
+ - **Historical trending** — `diagnose` is stateless; it reports on the current state of cumulative stats. Continuous monitoring is out of scope.
1339
+ - **Niche server checks** (WAL bloat, replication slot age, etc.) — better covered by your Postgres provider's monitoring or a dedicated tool; users on self-hosted setups that need them typically have their own tooling.
1340
+
1260
1341
  ## Settings
1261
1342
 
1262
1343
  The connection is configured with a single URL (`POSTGRES_URL`). `DATABASE_URL` is read as a platform-compat fallback. Set the URL to `none` to explicitly disable the database (e.g. during Docker image builds).
@@ -1,5 +1,20 @@
1
1
  # plain-postgres changelog
2
2
 
3
+ ## [0.99.0](https://github.com/dropseed/plain/releases/plain-postgres@0.99.0) (2026-04-23)
4
+
5
+ ### What's changed
6
+
7
+ - **Reworked `plain postgres diagnose` around tiered findings.** Warnings are now reserved for things the user can fix by editing model code or taking an app-level action — every warning carries a copy-paste fix or a model-file pointer (`app/path.py :: ModelName`). Noisy one-off signals (cache/index hit ratios, XID wraparound, connection saturation, pg_stat_statements availability, stats reset age) render as **informational context**; DB-state facts whose remedies live outside Plain (stats freshness, vacuum health, index bloat) render as **operational context** instead of warnings. Added `--verbose` to expand every check, and `--all` still includes installed-package tables. ([26abb6cbc075](https://github.com/dropseed/plain/commit/26abb6cbc075))
8
+ - **New diagnostic checks:** `stats_freshness` (uses `pg_class.reltuples` so it survives `pg_stat_reset`), `index_bloat` (ioguix btree estimator, public schema only), `missing_index_candidates` (seq-scan heuristics with per-query drill-down from `pg_stat_statements`), `blocking_queries` (wait age from `pg_locks.waitstart`, PG 14+), and `long_running_connections` (xact age for idle-in-transaction). Findings include **cross-check caveats** — e.g. an `unused_indexes` finding on a table that's also flagged by `stats_freshness` or `vacuum_health` now carries a warning that dropping the index may be premature. ([26abb6cbc075](https://github.com/dropseed/plain/commit/26abb6cbc075))
9
+ - **Permission-safe probes.** Checks that may hit permission errors (`pg_stat_statements`, `pg_stat_activity`, `pg_locks`) now wrap their queries in `cursor.connection.transaction()` so a failure rolls back cleanly in either autocommit or transaction mode without cascade-failing later checks. ([26abb6cbc075](https://github.com/dropseed/plain/commit/26abb6cbc075))
10
+ - **Refactored internals.** The 1800+ line `introspection/health.py` split into an `introspection/health/` package along natural seams (types, ownership, context, helpers, checks grouped by `structural`/`cumulative`/`snapshot`, and a runner). Public re-exports are unchanged. ([26abb6cbc075](https://github.com/dropseed/plain/commit/26abb6cbc075))
11
+ - Adapter annotations use `Response` after plain 0.135.0 merged `ResponseBase` into `Response`. ([f5007281d7fa](https://github.com/dropseed/plain/commit/f5007281d7fa))
12
+
13
+ ### Upgrade instructions
14
+
15
+ - Requires `plain>=0.135.0`.
16
+ - No code changes required. If you parse `plain postgres diagnose --json`, note the new `tier` field on each finding (`"structural"`, `"cumulative"`, `"snapshot"`, or `"operational"`) — operational findings still carry `status: "warning"` but the CLI renders them as context rather than as alarming warnings.
17
+
3
18
  ## [0.98.0](https://github.com/dropseed/plain/releases/plain-postgres@0.98.0) (2026-04-22)
4
19
 
5
20
  ### What's changed
@@ -1184,37 +1184,110 @@ graph TB
1184
1184
 
1185
1185
  ## Diagnostics
1186
1186
 
1187
- You can run health checks against your database to find issues like missing indexes, redundant indexes, and configuration problems.
1187
+ Run health checks against your database. `diagnose` is designed to produce
1188
+ only actionable findings — every warning has a copy-paste fix or specific
1189
+ resource to investigate, and noisy one-off signals (hit ratios, XID age) are
1190
+ surfaced as informational context rather than as warnings.
1188
1191
 
1189
1192
  ```bash
1190
1193
  uv run plain postgres diagnose
1191
1194
  ```
1192
1195
 
1193
- Use `--json` for structured output (useful for scripting and AI agents):
1196
+ Output modes:
1194
1197
 
1195
1198
  ```bash
1196
- uv run plain postgres diagnose --json
1199
+ uv run plain postgres diagnose --json # structured output for scripts/agents
1200
+ uv run plain postgres diagnose --verbose # expand to show every check, including passing
1201
+ uv run plain postgres diagnose --all # include findings on installed-package tables
1197
1202
  ```
1198
1203
 
1199
- Use `--all` to include issues in installed packages (by default, only your app's issues are shown):
1204
+ ### Guiding principle
1200
1205
 
1201
- ```bash
1202
- uv run plain postgres diagnose --all
1203
- ```
1206
+ `diagnose` emits a **warning** only if the remedy fits in the user's codebase
1207
+ or is an app-level action they own. If the remedy is "run SQL against your
1208
+ DB" or "configure your Postgres server," the check emits **operational
1209
+ context** or an **informational number**, not a warning. This keeps the
1210
+ warning surface high-trust — every warning has an edit-to-make — and prevents
1211
+ `diagnose` from bleeding into DB-host concerns.
1212
+
1213
+ ### Warning-tier checks
1214
+
1215
+ Things the user can fix by editing code + running `plain postgres sync`, or
1216
+ app-level incidents they must act on.
1217
+
1218
+ **Structural — always-real; a fix is possible immediately.**
1219
+
1220
+ | Check | What it finds | Severity |
1221
+ | ----------------------- | --------------------------------------------------------------------------------------------------- | ---------------- |
1222
+ | **Invalid indexes** | Broken indexes from failed `CREATE INDEX CONCURRENTLY` — maintained on writes, never used for reads | Warning |
1223
+ | **Duplicate indexes** | One index is a column-prefix of another on the same table | Warning |
1224
+ | **Missing FK indexes** | Foreign key columns without any index coverage | Warning |
1225
+ | **Sequence exhaustion** | Identity sequences approaching their type max | Warning/Critical |
1226
+
1227
+ **Cumulative — depends on stats since the last reset.**
1228
+
1229
+ | Check | What it finds | Severity |
1230
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- |
1231
+ | **Unused indexes** | Indexes with zero scans since stats reset (>1 MB). Excludes unique, constraint-backing, and sole-FK-coverage indexes | Warning |
1232
+ | **Missing index candidates** | Tables with seq-scan activity suggesting a missing index. Includes top contributing queries from pg_stat_statements | Warning |
1233
+
1234
+ **Snapshot — point-in-time incidents.**
1235
+
1236
+ | Check | What it finds | Severity |
1237
+ | ---------------------------- | ----------------------------------------------------------------------- | ---------------- |
1238
+ | **Long-running connections** | Client backends idle-in-transaction or running a query past a threshold | Warning/Critical |
1239
+ | **Blocking queries** | Queries currently blocking other queries via held locks | Warning/Critical |
1240
+
1241
+ ### Operational-context findings
1242
+
1243
+ These are facts about the database whose remedies live outside Plain today
1244
+ (`ANALYZE`, `VACUUM`, `REINDEX`, autovacuum server tuning). They're surfaced
1245
+ so agents and humans can interpret findings correctly, but the CLI renders
1246
+ them as context rather than alarming warnings — the user can't express the
1247
+ fix in their model code. (In JSON output each finding still carries
1248
+ `status: "warning"`; the `tier: "operational"` field is what distinguishes
1249
+ it.) Each finding still carries the exact SQL in its suggestion for anyone
1250
+ who wants to act.
1251
+
1252
+ | Finding | What it reports |
1253
+ | ------------------- | -------------------------------------------------------------------------------- |
1254
+ | **Stats freshness** | Tables whose planner statistics are missing (never analyzed) or stale |
1255
+ | **Vacuum health** | Tables with >10% dead tuples |
1256
+ | **Index bloat** | btree indexes with significant estimated wasted space (≥10 MB, ioguix estimator) |
1257
+
1258
+ If a future release exposes per-table autovacuum / fillfactor parameters in
1259
+ `model_options` (see the `postgres-model-storage-parameters` arc), these
1260
+ findings can graduate back to the warning tier — because the remedy will be
1261
+ expressible in code.
1204
1262
 
1205
- ### Checks
1263
+ ### Informational context
1206
1264
 
1207
- | Check | What it finds | Severity |
1208
- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
1209
- | **Invalid indexes** | Broken indexes from failed `CREATE INDEX CONCURRENTLY` maintained on writes, never used for reads | Warning |
1210
- | **Duplicate indexes** | Indexes where one is a column-prefix of another on the same table (e.g., an auto FK index that's redundant with a composite index) | Warning |
1211
- | **Unused indexes** | Indexes with zero scans since stats reset (>1 MB). Excludes unique indexes, constraint-backing indexes, and indexes that are the sole coverage for a FK column | Warning |
1212
- | **Missing FK indexes** | Foreign key columns without any index coverage parent DELETE/UPDATE operations will sequentially scan the child table | Warning |
1213
- | **Sequence exhaustion** | Identity sequences approaching their type max (>50% warning, >90% critical) | Warning/Critical |
1214
- | **XID wraparound** | Transaction ID age approaching the 2 billion wraparound limit (>25% warning, >40% critical) | Warning/Critical |
1215
- | **Cache hit ratio** | Heap buffer hit ratio below 98.5% — indicates insufficient `shared_buffers` or RAM | Warning |
1216
- | **Index hit ratio** | Index buffer hit ratio below 98.5% | Warning |
1217
- | **Vacuum health** | Tables with significant dead tuple accumulation (>10% of live rows) where autovacuum may be falling behind | Warning |
1265
+ Alongside checks, `diagnose` surfaces context an agent or human may want to read but that isn't actionable on its own:
1266
+
1267
+ - **Cache hit ratio**, **Index hit ratio** buffer hit rates (volatile after restart; not a warning in themselves)
1268
+ - **XID wraparound** transaction ID age as a percent of the 2B limit. Autovacuum usually keeps this low; long-running transactions can block the freeze process even on managed Postgres
1269
+ - **Connection saturation** active/max connections at this moment
1270
+ - **Stats reset** when cumulative stats were last reset (affects the confidence of operational checks)
1271
+ - **pg_stat_statements** whether the extension is installed
1272
+
1273
+ ### Cross-check caveats
1274
+
1275
+ Findings whose confidence depends on another check are tagged with a caveat. For example:
1276
+
1277
+ - `unused_indexes` on a table flagged by `stats_freshness` → caveat: "this table has never been analyzed — the planner may not yet use this index; re-check after running ANALYZE"
1278
+ - `missing_index_candidates` on a never-analyzed table → caveat: "planner statistics are absent — running ANALYZE may change query plans and make this finding moot"
1279
+
1280
+ This prevents false confidence: dropping an "unused" index on a never-analyzed table is often the wrong move.
1281
+
1282
+ ### Model-aware suggestions
1283
+
1284
+ Findings on app-owned tables include the Plain model class and its source file. Suggestions reference the exact edit point:
1285
+
1286
+ ```
1287
+ app/processing/models.py :: ProcessingResult — Add an Index on ["is_processing"] to the model, then run plain postgres sync
1288
+ ```
1289
+
1290
+ This closes the loop from detection to fix — agents can draft the model edit without guessing.
1218
1291
 
1219
1292
  ### App vs package issues
1220
1293
 
@@ -1234,6 +1307,8 @@ heroku run -a your-app "plain postgres diagnose --json"
1234
1307
 
1235
1308
  The `--json` flag must be quoted so Heroku passes it through to the command.
1236
1309
 
1310
+ Cumulative-stat checks (`stats_freshness`, `vacuum_health`, `unused_indexes`, `missing_index_candidates`, `index_bloat`) need cumulative stat history after the last reset to be reliable. Check the `stats_reset` informational to see how much history you have. (Note: this list spans both the warning and operational tiers — the common thread is that all five depend on counters that `pg_stat_reset()` wipes.)
1311
+
1237
1312
  ### Preflight checks
1238
1313
 
1239
1314
  Two related checks run automatically during `uv run plain preflight` (and `uv run plain check`):
@@ -1243,6 +1318,12 @@ Two related checks run automatically during `uv run plain preflight` (and `uv ru
1243
1318
 
1244
1319
  These are static, code-level checks that catch issues before you deploy. The `diagnose` command complements them with runtime stats from the actual database.
1245
1320
 
1321
+ ### What diagnose deliberately doesn't do
1322
+
1323
+ - **LLM-powered column recommendations for missing indexes** — `missing_index_candidates` shows the culprit queries and lets you decide. For precise column-level suggestions, use a platform tool (PlanetScale Insights, Dexter, pg_qualstats + hypopg).
1324
+ - **Historical trending** — `diagnose` is stateless; it reports on the current state of cumulative stats. Continuous monitoring is out of scope.
1325
+ - **Niche server checks** (WAL bloat, replication slot age, etc.) — better covered by your Postgres provider's monitoring or a dedicated tool; users on self-hosted setups that need them typically have their own tooling.
1326
+
1246
1327
  ## Settings
1247
1328
 
1248
1329
  The connection is configured with a single URL (`POSTGRES_URL`). `DATABASE_URL` is read as a platform-compat fallback. Set the URL to `none` to explicitly disable the database (e.g. during Docker image builds).