plain.postgres 0.104.0__tar.gz → 0.105.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 (214) hide show
  1. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/PKG-INFO +1 -1
  2. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/CHANGELOG.md +14 -0
  3. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/agents/.claude/rules/plain-postgres.md +22 -3
  4. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/base.py +1 -1
  5. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/base.py +15 -4
  6. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/binary.py +3 -1
  7. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/boolean.py +1 -1
  8. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/duration.py +3 -1
  9. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/encrypted.py +3 -1
  10. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/network.py +1 -1
  11. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/numeric.py +7 -5
  12. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/temporal.py +9 -3
  13. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/text.py +4 -4
  14. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/timezones.py +3 -1
  15. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/uuid.py +1 -1
  16. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/types.py +7 -7
  17. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/types.pyi +142 -58
  18. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/pyproject.toml +1 -1
  19. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/constraints.py +2 -2
  20. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/defaults.py +8 -11
  21. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/delete.py +17 -29
  22. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/encrypted.py +4 -4
  23. plain_postgres-0.105.0/tests/app/examples/models/forms.py +31 -0
  24. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/indexes.py +2 -2
  25. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/iteration.py +2 -2
  26. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/mixins.py +3 -5
  27. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/nullability.py +1 -1
  28. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/querysets.py +3 -3
  29. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/relationships.py +4 -4
  30. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/storage_parameters.py +1 -1
  31. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/trees.py +1 -1
  32. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_autodetector_not_null_errors.py +15 -15
  33. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_autodetector_type_change.py +6 -6
  34. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_fk_characterization.py +1 -1
  35. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_delete_behaviors.py +1 -0
  36. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_related.py +2 -2
  37. plain_postgres-0.104.0/tests/app/examples/models/forms.py +0 -35
  38. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/.gitignore +0 -0
  39. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/CLAUDE.md +0 -0
  40. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/LICENSE +0 -0
  41. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/README.md +0 -0
  42. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/README.md +0 -0
  43. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/__init__.py +0 -0
  44. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/adapters.py +0 -0
  45. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/agents/.claude/skills/plain-postgres-doctor/SKILL.md +0 -0
  46. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/aggregates.py +0 -0
  47. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/cli/__init__.py +0 -0
  48. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/cli/converge.py +0 -0
  49. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/cli/core.py +0 -0
  50. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/cli/decorators.py +0 -0
  51. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/cli/diagnose.py +0 -0
  52. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/cli/migrations.py +0 -0
  53. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/cli/schema.py +0 -0
  54. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/cli/sync.py +0 -0
  55. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/config.py +0 -0
  56. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/connection.py +0 -0
  57. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/constants.py +0 -0
  58. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/constraints.py +0 -0
  59. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/convergence/__init__.py +0 -0
  60. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/convergence/analysis.py +0 -0
  61. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/convergence/fixes.py +0 -0
  62. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/convergence/planning.py +0 -0
  63. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/database_url.py +0 -0
  64. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/db.py +0 -0
  65. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/ddl.py +0 -0
  66. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/default_settings.py +0 -0
  67. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/deletion.py +0 -0
  68. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/dialect.py +0 -0
  69. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/entrypoints.py +0 -0
  70. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/enums.py +0 -0
  71. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/exceptions.py +0 -0
  72. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/expressions.py +0 -0
  73. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/__init__.py +0 -0
  74. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/json.py +0 -0
  75. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/mixins.py +0 -0
  76. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/primary_key.py +0 -0
  77. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/related.py +0 -0
  78. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/related_descriptors.py +0 -0
  79. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/related_lookups.py +0 -0
  80. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/related_managers.py +0 -0
  81. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/reverse_descriptors.py +0 -0
  82. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/fields/reverse_related.py +0 -0
  83. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/forms.py +0 -0
  84. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/__init__.py +0 -0
  85. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/comparison.py +0 -0
  86. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/datetime.py +0 -0
  87. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/math.py +0 -0
  88. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/mixins.py +0 -0
  89. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/random.py +0 -0
  90. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/text.py +0 -0
  91. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/uuid.py +0 -0
  92. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/functions/window.py +0 -0
  93. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/indexes.py +0 -0
  94. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/__init__.py +0 -0
  95. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/__init__.py +0 -0
  96. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/checks_cumulative.py +0 -0
  97. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/checks_snapshot.py +0 -0
  98. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/checks_structural.py +0 -0
  99. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/context.py +0 -0
  100. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/helpers.py +0 -0
  101. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/ownership.py +0 -0
  102. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/runner.py +0 -0
  103. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/health/types.py +0 -0
  104. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/introspection/schema.py +0 -0
  105. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/lookups.py +0 -0
  106. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/meta.py +0 -0
  107. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/middleware.py +0 -0
  108. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/__init__.py +0 -0
  109. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/autodetector.py +0 -0
  110. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/exceptions.py +0 -0
  111. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/executor.py +0 -0
  112. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/graph.py +0 -0
  113. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/loader.py +0 -0
  114. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/migration.py +0 -0
  115. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/operations/__init__.py +0 -0
  116. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/operations/base.py +0 -0
  117. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/operations/fields.py +0 -0
  118. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/operations/models.py +0 -0
  119. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/operations/special.py +0 -0
  120. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/optimizer.py +0 -0
  121. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/questioner.py +0 -0
  122. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/recorder.py +0 -0
  123. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/serializer.py +0 -0
  124. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/state.py +0 -0
  125. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/utils.py +0 -0
  126. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/migrations/writer.py +0 -0
  127. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/options.py +0 -0
  128. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/otel.py +0 -0
  129. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/preflight.py +0 -0
  130. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/query.py +0 -0
  131. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/query_utils.py +0 -0
  132. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/registry.py +0 -0
  133. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/schema.py +0 -0
  134. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/sources.py +0 -0
  135. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/sql/__init__.py +0 -0
  136. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/sql/compiler.py +0 -0
  137. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/sql/constants.py +0 -0
  138. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/sql/datastructures.py +0 -0
  139. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/sql/query.py +0 -0
  140. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/sql/where.py +0 -0
  141. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/test/__init__.py +0 -0
  142. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/test/database.py +0 -0
  143. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/test/pytest.py +0 -0
  144. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/transaction.py +0 -0
  145. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/plain/postgres/utils.py +0 -0
  146. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/forms.py +0 -0
  147. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0001_initial.py +0 -0
  148. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
  149. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
  150. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0004_defaultquerysetmodel_mixintestmodel_and_more.py +0 -0
  151. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0005_feature_carfeature_car_features.py +0 -0
  152. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0006_secretstore.py +0 -0
  153. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0007_treenode_unconstrainedchild.py +0 -0
  154. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0008_setsentinelparent_diamondparenta_midparent_and_more.py +0 -0
  155. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0009_circb_circa_circb_partner.py +0 -0
  156. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0010_hideableitem.py +0 -0
  157. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0011_defaultsexample.py +0 -0
  158. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0012_iterationexample.py +0 -0
  159. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0013_indexexample_constraintexample_nullabilityexample.py +0 -0
  160. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0014_widget_rename_feature_tag_remove_carfeature_car_and_more.py +0 -0
  161. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0015_dbdefaultsexample.py +0 -0
  162. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0016_formsexample.py +0 -0
  163. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0017_random_string_token.py +0 -0
  164. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/0018_storageparametersexample.py +0 -0
  165. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/migrations/__init__.py +0 -0
  166. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/__init__.py +0 -0
  167. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/models/unregistered.py +0 -0
  168. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/urls.py +0 -0
  169. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/examples/views.py +0 -0
  170. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/settings.py +0 -0
  171. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/app/urls.py +0 -0
  172. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/conftest.py +0 -0
  173. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/conftest_convergence.py +0 -0
  174. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_connection_isolation.py +0 -0
  175. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_connection_lifecycle.py +0 -0
  176. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_connection_pool.py +0 -0
  177. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_constraint_violation_error.py +0 -0
  178. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_convergence.py +0 -0
  179. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_convergence_constraints.py +0 -0
  180. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_convergence_defaults.py +0 -0
  181. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_convergence_fk.py +0 -0
  182. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_convergence_indexes.py +0 -0
  183. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_convergence_nullability.py +0 -0
  184. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_convergence_storage_parameters.py +0 -0
  185. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_convergence_timeouts.py +0 -0
  186. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_db_expression_defaults.py +0 -0
  187. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_diagnose.py +0 -0
  188. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_executor_connection_hook.py +0 -0
  189. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_health.py +0 -0
  190. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_introspection.py +0 -0
  191. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_literal_default_persistence.py +0 -0
  192. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_management_connection.py +0 -0
  193. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_migration_executor.py +0 -0
  194. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_no_callable_defaults.py +0 -0
  195. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_otel_metrics.py +0 -0
  196. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_preflight_duplicate_indexes.py +0 -0
  197. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_preflight_fk_coverage.py +0 -0
  198. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_schema_normalize_type.py +0 -0
  199. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/internal/test_schema_timeouts.py +0 -0
  200. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_database_url.py +0 -0
  201. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_deferred_loading.py +0 -0
  202. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_encrypted_fields.py +0 -0
  203. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_exceptions.py +0 -0
  204. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_field_defaults.py +0 -0
  205. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_functions_uuid.py +0 -0
  206. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_iterator.py +0 -0
  207. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_m2m.py +0 -0
  208. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_manager_assignment.py +0 -0
  209. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_mixins.py +0 -0
  210. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_modelform_roundtrip.py +0 -0
  211. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_queryset_repr.py +0 -0
  212. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_random_string_field.py +0 -0
  213. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_raw_query.py +0 -0
  214. {plain_postgres-0.104.0 → plain_postgres-0.105.0}/tests/public/test_read_only_transactions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.postgres
3
- Version: 0.104.0
3
+ Version: 0.105.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
@@ -1,5 +1,19 @@
1
1
  # plain-postgres changelog
2
2
 
3
+ ## [0.105.0](https://github.com/dropseed/plain/releases/plain-postgres@0.105.0) (2026-05-25)
4
+
5
+ ### What's changed
6
+
7
+ - **Field constructors in `plain.postgres.types` are now typed as parameterized descriptors.** Stubs return `XField[T]` (e.g. `TextField[str]`, `IntegerField[int | None]` when `allow_null=True`) instead of the primitive value. Combined with `Field[T]`'s overloaded `__get__`, class-level access (`User.email`) sees the descriptor and instance access (`user.email`) sees the value — no annotation needed on model declarations. ([229ecdbbfa](https://github.com/dropseed/plain/commit/229ecdbbfa))
8
+ - **`Field.__set__` is now typed as `value: T`** instead of `Any`, so a type checker flags `row.name = 123` on a `TextField` at the call site. Runtime conversion via `to_python()` is unchanged. ([229ecdbbfa](https://github.com/dropseed/plain/commit/229ecdbbfa))
9
+ - **`ForeignKeyField` overloads split by `to=` argument shape.** Passing a model class (`to=Author`) returns `_ForeignKeyDescriptor[T, V]` and accepts bare-PK assignment (`book.author = 5`) via `__set__`. Passing a string forward reference (`to="Author"`) returns the bare value type and requires an LHS annotation so the type can bind. ([229ecdbbfa](https://github.com/dropseed/plain/commit/229ecdbbfa))
10
+ - `JSONField` and `EncryptedJSONField` keep explicit annotations on model declarations — their runtime classes aren't generic, so the stubs return `Any`. ([229ecdbbfa](https://github.com/dropseed/plain/commit/229ecdbbfa))
11
+
12
+ ### Upgrade instructions
13
+
14
+ - **Drop primitive type annotations on model field declarations.** `name: str = types.TextField()` will now fail type checking because the right-hand side is typed `TextField[str]`, not `str`. Change to `name = types.TextField()` — type checkers still infer `str` at instance access through the descriptor's `__get__`. Runtime is unchanged either way.
15
+ - **Keep explicit annotations for `JSONField` / `EncryptedJSONField`** — these still return `Any` from the stubs, so the declared type is what flows through. Example: `parameters: dict[str, Any] | None = types.JSONField(required=False, allow_null=True)`.
16
+
3
17
  ## [0.104.0](https://github.com/dropseed/plain/releases/plain-postgres@0.104.0) (2026-05-22)
4
18
 
5
19
  ### What's changed
@@ -7,13 +7,32 @@ paths:
7
7
 
8
8
  ## Field Imports
9
9
 
10
- Import fields via `from plain.postgres import types` and annotate with Python types:
10
+ Import fields via `from plain.postgres import types`. Don't add primitive
11
+ annotations — the field stubs return typed descriptors that resolve to the
12
+ right value type on instance access:
11
13
 
12
14
  ```python
13
15
  from plain.postgres import types
14
16
 
15
- name: str = types.TextField(max_length=100)
16
- car: Car = types.ForeignKeyField("Car", on_delete=postgres.CASCADE)
17
+ name = types.TextField(max_length=100)
18
+ car = types.ForeignKeyField(Car, on_delete=postgres.CASCADE)
19
+ ```
20
+
21
+ For string forward references (`"self"`, `"OtherModel"`), the type checker
22
+ can't infer the target type from the string — annotate explicitly so
23
+ instance access keeps its type:
24
+
25
+ ```python
26
+ parent: TreeNode | None = types.ForeignKeyField("self", on_delete=postgres.CASCADE, allow_null=True)
27
+ ```
28
+
29
+ For `JSONField` and `EncryptedJSONField`, the stub returns `Any` (the
30
+ runtime class isn't generic over its value shape), so annotate explicitly
31
+ to preserve typing:
32
+
33
+ ```python
34
+ parameters: dict[str, Any] | None = types.JSONField(required=False, allow_null=True)
35
+ config: dict | None = types.EncryptedJSONField(required=False, allow_null=True)
17
36
  ```
18
37
 
19
38
  Do NOT import field classes directly from `plain.postgres` or `plain.postgres.fields`.
@@ -83,7 +83,7 @@ class ModelState:
83
83
 
84
84
  class Model(metaclass=ModelBase):
85
85
  # Every model gets an automatic id field
86
- id: int = types.PrimaryKeyField()
86
+ id = types.PrimaryKeyField()
87
87
 
88
88
  # Descriptors for other model behavior
89
89
  query: QuerySet[Self] = QuerySet()
@@ -437,12 +437,18 @@ class Field[T](RegisterLookupMixin):
437
437
 
438
438
  return cast(T, data.get(field_name))
439
439
 
440
- def __set__(self, instance: Model, value: Any) -> None:
440
+ def __set__(self, instance: Model, value: T) -> None:
441
441
  """
442
442
  Descriptor __set__ for attribute assignment.
443
443
 
444
444
  Validates and converts the value using to_python(), then stores it
445
445
  in instance.__dict__[name].
446
+
447
+ The parameter is typed `T` (the field's value type) so a type checker
448
+ rejects assigning incompatible types — `row.name = 123` on a
449
+ TextField is caught at the call site. The runtime is more permissive
450
+ (to_python converts strings → ints etc.), but encouraging explicit
451
+ conversion at the boundary is the better default.
446
452
  """
447
453
  # Safety check: ensure field has been properly initialized
448
454
  if not hasattr(self, "column"):
@@ -454,12 +460,17 @@ class Field[T](RegisterLookupMixin):
454
460
 
455
461
  # Convert/validate the value. The DATABASE_DEFAULT sentinel is stored
456
462
  # as-is so the INSERT compiler can emit `DEFAULT` in the VALUES clause.
457
- if value is not None and value is not DATABASE_DEFAULT:
458
- value = self.to_python(value)
463
+ # Use a separate local so the parameter's narrow `T` type isn't
464
+ # widened by to_python's `T | None` return.
465
+ stored: Any
466
+ if value is None or value is DATABASE_DEFAULT:
467
+ stored = value
468
+ else:
469
+ stored = self.to_python(value)
459
470
 
460
471
  # Store in instance dict
461
472
  assert self.name is not None
462
- instance.__dict__[self.name] = value
473
+ instance.__dict__[self.name] = stored
463
474
 
464
475
  def __delete__(self, instance: Model) -> None:
465
476
  """
@@ -15,7 +15,9 @@ if TYPE_CHECKING:
15
15
  from plain.postgres.sql.compiler import SQLCompiler
16
16
 
17
17
 
18
- class BinaryField(ColumnField[bytes | memoryview]):
18
+ class BinaryField[
19
+ T: (bytes | memoryview, bytes | memoryview | None) = bytes | memoryview
20
+ ](ColumnField[T]):
19
21
  db_type_sql = "bytea"
20
22
  empty_values = [None, b""]
21
23
  _default_empty_value = b""
@@ -7,7 +7,7 @@ from plain import exceptions
7
7
  from .base import DefaultableField
8
8
 
9
9
 
10
- class BooleanField(DefaultableField[bool]):
10
+ class BooleanField[T: (bool, bool | None) = bool](DefaultableField[T]):
11
11
  db_type_sql = "boolean"
12
12
  empty_strings_allowed = False
13
13
 
@@ -13,7 +13,9 @@ if TYPE_CHECKING:
13
13
  from plain.postgres.connection import DatabaseConnection
14
14
 
15
15
 
16
- class DurationField(DefaultableField[datetime.timedelta]):
16
+ class DurationField[
17
+ T: (datetime.timedelta, datetime.timedelta | None) = datetime.timedelta
18
+ ](DefaultableField[T]):
17
19
  """Store timedelta objects using PostgreSQL's interval type."""
18
20
 
19
21
  db_type_sql = "interval"
@@ -178,7 +178,9 @@ class EncryptedFieldMixin:
178
178
  return errors
179
179
 
180
180
 
181
- class EncryptedTextField(EncryptedFieldMixin, ColumnField[str]):
181
+ class EncryptedTextField[T: (str, str | None) = str](
182
+ EncryptedFieldMixin, ColumnField[T]
183
+ ):
182
184
  """A text field that encrypts its value before storing in the database.
183
185
 
184
186
  Values are encrypted using Fernet (AES-128-CBC + HMAC-SHA256) with a key
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  from plain.postgres.connection import DatabaseConnection
16
16
 
17
17
 
18
- class GenericIPAddressField(DefaultableField[str]):
18
+ class GenericIPAddressField[T: (str, str | None) = str](DefaultableField[T]):
19
19
  db_type_sql = "inet"
20
20
  empty_strings_allowed = False
21
21
 
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
16
16
  from plain.postgres.connection import DatabaseConnection
17
17
 
18
18
 
19
- class FloatField(DefaultableField[float]):
19
+ class FloatField[T: (float, float | None) = float](DefaultableField[T]):
20
20
  db_type_sql = "double precision"
21
21
  empty_strings_allowed = False
22
22
 
@@ -44,7 +44,7 @@ class FloatField(DefaultableField[float]):
44
44
  )
45
45
 
46
46
 
47
- class IntegerField(DefaultableField[int]):
47
+ class IntegerField[T: (int, int | None) = int](DefaultableField[T]):
48
48
  db_type_sql = "integer"
49
49
  integer_range: tuple[int, int] = (-2147483648, 2147483647)
50
50
  psycopg_type: type = numeric.Int4
@@ -118,19 +118,21 @@ class IntegerField(DefaultableField[int]):
118
118
  )
119
119
 
120
120
 
121
- class BigIntegerField(IntegerField):
121
+ class BigIntegerField[T: (int, int | None) = int](IntegerField[T]):
122
122
  db_type_sql = "bigint"
123
123
  integer_range = (-9223372036854775808, 9223372036854775807)
124
124
  psycopg_type = numeric.Int8
125
125
 
126
126
 
127
- class SmallIntegerField(IntegerField):
127
+ class SmallIntegerField[T: (int, int | None) = int](IntegerField[T]):
128
128
  db_type_sql = "smallint"
129
129
  integer_range = (-32768, 32767)
130
130
  psycopg_type = numeric.Int2
131
131
 
132
132
 
133
- class DecimalField(DefaultableField[decimal.Decimal]):
133
+ class DecimalField[T: (decimal.Decimal, decimal.Decimal | None) = decimal.Decimal](
134
+ DefaultableField[T]
135
+ ):
134
136
  db_type_sql = "numeric(%(max_digits)s,%(decimal_places)s)"
135
137
  empty_strings_allowed = False
136
138
 
@@ -69,7 +69,9 @@ def _check_if_value_fixed(
69
69
  return []
70
70
 
71
71
 
72
- class DateField(DefaultableField[datetime.date]):
72
+ class DateField[T: (datetime.date, datetime.date | None) = datetime.date](
73
+ DefaultableField[T]
74
+ ):
73
75
  db_type_sql = "date"
74
76
  empty_strings_allowed = False
75
77
 
@@ -149,7 +151,9 @@ class DateField(DefaultableField[datetime.date]):
149
151
  return value
150
152
 
151
153
 
152
- class DateTimeField(ColumnField[datetime.datetime]):
154
+ class DateTimeField[
155
+ T: (datetime.datetime, datetime.datetime | None) = datetime.datetime
156
+ ](ColumnField[T]):
153
157
  db_type_sql = "timestamp with time zone"
154
158
  empty_strings_allowed = False
155
159
 
@@ -302,7 +306,9 @@ class DateTimeField(ColumnField[datetime.datetime]):
302
306
  return value
303
307
 
304
308
 
305
- class TimeField(DefaultableField[datetime.time]):
309
+ class TimeField[T: (datetime.time, datetime.time | None) = datetime.time](
310
+ DefaultableField[T]
311
+ ):
306
312
  db_type_sql = "time without time zone"
307
313
  empty_strings_allowed = False
308
314
 
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
13
13
  from plain.postgres.functions.random import RandomString
14
14
 
15
15
 
16
- class TextField(ChoicesField[str]):
16
+ class TextField[T: (str, str | None) = str](ChoicesField[T]):
17
17
  db_type_sql = "text"
18
18
 
19
19
  def __init__(
@@ -87,15 +87,15 @@ class TextField(ChoicesField[str]):
87
87
  return self.to_python(value)
88
88
 
89
89
 
90
- class EmailField(TextField):
90
+ class EmailField[T: (str, str | None) = str](TextField[T]):
91
91
  default_validators = [validators.validate_email]
92
92
 
93
93
 
94
- class URLField(TextField):
94
+ class URLField[T: (str, str | None) = str](TextField[T]):
95
95
  default_validators = [validators.URLValidator()]
96
96
 
97
97
 
98
- class RandomStringField(ColumnField[str]):
98
+ class RandomStringField[T: (str, str | None) = str](ColumnField[T]):
99
99
  """Text column whose value is a Postgres-generated random hex string.
100
100
 
101
101
  The column carries a ``DEFAULT`` that evaluates per row, so raw SQL and
@@ -47,7 +47,9 @@ def _get_canonical_timezones() -> frozenset[str]:
47
47
  )
48
48
 
49
49
 
50
- class TimeZoneField(ChoicesField[zoneinfo.ZoneInfo]):
50
+ class TimeZoneField[
51
+ T: (zoneinfo.ZoneInfo, zoneinfo.ZoneInfo | None) = zoneinfo.ZoneInfo
52
+ ](ChoicesField[T]):
51
53
  """
52
54
  A model field that stores timezone names as strings but provides ZoneInfo objects.
53
55
 
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
14
14
  from plain.postgres.expressions import Func
15
15
 
16
16
 
17
- class UUIDField(ColumnField[UUID]):
17
+ class UUIDField[T: (UUID, UUID | None) = UUID](ColumnField[T]):
18
18
  db_type_sql = "uuid"
19
19
  empty_strings_allowed = False
20
20
 
@@ -2,19 +2,19 @@
2
2
  Typed field imports for better IDE and type checker support.
3
3
 
4
4
  This module provides the same field classes as plain.postgres.fields,
5
- but with a companion .pyi stub file that makes type checkers interpret
6
- field assignments as their primitive Python types.
5
+ with a companion .pyi stub file that types each constructor as the
6
+ typed descriptor (`XField[T]`). Combined with the descriptor's overloaded
7
+ `__get__`, type checkers see the field reference at the class level and
8
+ the primitive value at instance access — no annotation needed.
7
9
 
8
10
  Usage:
9
11
  from plain.postgres import types
10
12
 
11
13
  @postgres.register_model
12
14
  class User(postgres.Model):
13
- email: str = types.EmailField()
14
- age: int = types.IntegerField()
15
- is_active: bool = types.BooleanField(default=True)
16
-
17
- This is optional - you can continue using untyped field definitions.
15
+ email = types.EmailField()
16
+ age = types.IntegerField()
17
+ is_active = types.BooleanField(default=True)
18
18
  """
19
19
 
20
20
  # Re-export scalar field types