plain.postgres 0.103.2__tar.gz → 0.103.3__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.
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/PKG-INFO +1 -1
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/CHANGELOG.md +11 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/related.py +1 -1
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/query.py +7 -1
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/pyproject.toml +1 -1
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_m2m.py +29 -0
- plain_postgres-0.103.3/tests/public/test_queryset_repr.py +60 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/.gitignore +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/CLAUDE.md +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/LICENSE +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/README.md +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/README.md +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/adapters.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/agents/.claude/rules/plain-postgres.md +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/agents/.claude/skills/plain-postgres-doctor/SKILL.md +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/aggregates.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/base.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/cli/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/cli/converge.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/cli/core.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/cli/decorators.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/cli/diagnose.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/cli/migrations.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/cli/schema.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/cli/sync.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/config.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/connection.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/constants.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/constraints.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/convergence/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/convergence/analysis.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/convergence/fixes.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/convergence/planning.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/database_url.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/db.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/ddl.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/default_settings.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/deletion.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/dialect.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/entrypoints.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/enums.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/exceptions.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/expressions.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/base.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/binary.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/boolean.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/duration.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/encrypted.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/json.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/mixins.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/network.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/numeric.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/primary_key.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/related_descriptors.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/related_lookups.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/related_managers.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/reverse_descriptors.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/reverse_related.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/temporal.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/text.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/timezones.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/uuid.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/forms.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/comparison.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/datetime.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/math.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/mixins.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/random.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/text.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/uuid.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/functions/window.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/indexes.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/checks_cumulative.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/checks_snapshot.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/checks_structural.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/context.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/helpers.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/ownership.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/runner.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/types.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/schema.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/lookups.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/meta.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/middleware.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/autodetector.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/exceptions.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/executor.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/graph.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/loader.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/migration.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/base.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/fields.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/models.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/special.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/optimizer.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/questioner.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/recorder.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/serializer.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/state.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/utils.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/writer.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/options.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/otel.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/preflight.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/query_utils.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/registry.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/schema.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/sources.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/sql/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/sql/compiler.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/sql/constants.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/sql/datastructures.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/sql/query.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/sql/where.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/test/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/test/database.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/test/pytest.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/transaction.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/types.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/types.pyi +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/utils.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/forms.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0001_initial.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0004_defaultquerysetmodel_mixintestmodel_and_more.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0005_feature_carfeature_car_features.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0006_secretstore.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0007_treenode_unconstrainedchild.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0008_setsentinelparent_diamondparenta_midparent_and_more.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0009_circb_circa_circb_partner.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0010_hideableitem.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0011_defaultsexample.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0012_iterationexample.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0013_indexexample_constraintexample_nullabilityexample.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0014_widget_rename_feature_tag_remove_carfeature_car_and_more.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0015_dbdefaultsexample.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0016_formsexample.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0017_random_string_token.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0018_storageparametersexample.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/__init__.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/constraints.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/defaults.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/delete.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/encrypted.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/forms.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/indexes.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/iteration.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/mixins.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/nullability.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/querysets.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/relationships.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/storage_parameters.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/trees.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/unregistered.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/urls.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/views.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/settings.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/urls.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/conftest.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/conftest_convergence.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_autodetector_not_null_errors.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_autodetector_type_change.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_connection_isolation.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_connection_lifecycle.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_connection_pool.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_constraint_violation_error.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_constraints.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_defaults.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_fk.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_indexes.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_nullability.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_storage_parameters.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_timeouts.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_db_expression_defaults.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_diagnose.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_executor_connection_hook.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_health.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_introspection.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_literal_default_persistence.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_management_connection.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_migration_executor.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_no_callable_defaults.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_otel_metrics.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_preflight_fk_coverage.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_schema_normalize_type.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_schema_timeouts.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_database_url.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_delete_behaviors.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_encrypted_fields.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_exceptions.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_field_defaults.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_functions_uuid.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_iterator.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_manager_assignment.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_mixins.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_modelform_roundtrip.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_random_string_field.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_raw_query.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_read_only_transactions.py +0 -0
- {plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_related.py +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# plain-postgres changelog
|
|
2
2
|
|
|
3
|
+
## [0.103.3](https://github.com/dropseed/plain/releases/plain-postgres@0.103.3) (2026-05-08)
|
|
4
|
+
|
|
5
|
+
### What's changed
|
|
6
|
+
|
|
7
|
+
- **`QuerySet.__repr__` no longer issues SQL for unevaluated querysets.** Error reporters (Sentry, pdb, exception templates) call `repr()` on stack-frame locals to build error events, and a surprise `SELECT … LIMIT 21` inside an exception path is a known footgun — especially on production where the underlying query may itself be the cause of the failure. Unevaluated querysets now render as `<QuerySet [unevaluated]>`; once the result cache is populated, `repr()` formats from the cache as before (still truncating past 20 rows). ([d8b7c4ec30](https://github.com/dropseed/plain/commit/d8b7c4ec30))
|
|
8
|
+
- **`ManyToManyField.value_from_object` no longer calls `.all()` on a manager.** The form-roundtrip path went through `getattr(obj, attname).all()`, which on the M2M manager dispatched to its descriptor and could trigger an unintended fetch/SQL path. Switched to the manager's `.query` queryset, which is the documented entry point. ([83da86b19b](https://github.com/dropseed/plain/commit/83da86b19b))
|
|
9
|
+
|
|
10
|
+
### Upgrade instructions
|
|
11
|
+
|
|
12
|
+
- No changes required. Note that interactive shell users who relied on `repr()` triggering evaluation (e.g., typing `qs` at the prompt to print rows) will now see `<QuerySet [unevaluated]>` — call `list(qs)` or slice it to materialize.
|
|
13
|
+
|
|
3
14
|
## [0.103.2](https://github.com/dropseed/plain/releases/plain-postgres@0.103.2) (2026-05-06)
|
|
4
15
|
|
|
5
16
|
### What's changed
|
|
@@ -1174,7 +1174,7 @@ class ManyToManyField(RelatedField):
|
|
|
1174
1174
|
pass
|
|
1175
1175
|
|
|
1176
1176
|
def value_from_object(self, obj: Model) -> list[Any]:
|
|
1177
|
-
return [] if obj.id is None else list(getattr(obj, self.attname).
|
|
1177
|
+
return [] if obj.id is None else list(getattr(obj, self.attname).query)
|
|
1178
1178
|
|
|
1179
1179
|
def save_form_data(self, instance: Model, data: Any) -> None:
|
|
1180
1180
|
getattr(instance, self.attname).set(data)
|
|
@@ -380,7 +380,13 @@ class QuerySet[T: "Model"]:
|
|
|
380
380
|
self.__dict__.update(state)
|
|
381
381
|
|
|
382
382
|
def __repr__(self) -> str:
|
|
383
|
-
|
|
383
|
+
# Don't run SQL from __repr__ — error reporters (Sentry, pdb,
|
|
384
|
+
# exception templates) call repr() on stack-frame locals to
|
|
385
|
+
# build error events. If the queryset hasn't been evaluated, a
|
|
386
|
+
# surprise SELECT inside an exception path is a known footgun.
|
|
387
|
+
if self._result_cache is None:
|
|
388
|
+
return f"<{self.__class__.__name__} [unevaluated]>"
|
|
389
|
+
data: list[Any] = list(self._result_cache[: REPR_OUTPUT_SIZE + 1])
|
|
384
390
|
if len(data) > REPR_OUTPUT_SIZE:
|
|
385
391
|
data[-1] = "...(remaining elements truncated)..."
|
|
386
392
|
return f"<{self.__class__.__name__} {data!r}>"
|
|
@@ -5,10 +5,13 @@ exercise ManyToManyField accessors, the through model, and the Widget-specific
|
|
|
5
5
|
unique constraint that produces a realistic ValidationError on duplicate create.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from typing import cast
|
|
9
|
+
|
|
8
10
|
import pytest
|
|
9
11
|
from app.examples.models.relationships import Tag, Widget, WidgetTag
|
|
10
12
|
|
|
11
13
|
from plain.exceptions import ValidationError
|
|
14
|
+
from plain.postgres.fields.related import ManyToManyField
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
def test_create_unique_constraint(db):
|
|
@@ -94,6 +97,32 @@ def test_many_to_many_clear(db):
|
|
|
94
97
|
assert widget.tags.query.count() == 0
|
|
95
98
|
|
|
96
99
|
|
|
100
|
+
def test_value_from_object_returns_related_objects(db):
|
|
101
|
+
"""ManyToManyField.value_from_object must return the currently-related
|
|
102
|
+
objects. ModelForm's `model_to_dict` calls this when given an instance
|
|
103
|
+
so the form can populate `initial` for the M2M field — a regression
|
|
104
|
+
here breaks UpdateView for any model with an M2M.
|
|
105
|
+
"""
|
|
106
|
+
widget = Widget.query.create(name="Subaru", size="Outback")
|
|
107
|
+
gps = Tag.query.create(name="GPS")
|
|
108
|
+
sunroof = Tag.query.create(name="Sunroof")
|
|
109
|
+
widget.tags.add(gps, sunroof)
|
|
110
|
+
|
|
111
|
+
field = cast(ManyToManyField, Widget._model_meta.get_forward_field("tags"))
|
|
112
|
+
result = field.value_from_object(widget)
|
|
113
|
+
|
|
114
|
+
assert {t.name for t in result} == {"GPS", "Sunroof"}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_value_from_object_unsaved_instance_returns_empty(db):
|
|
118
|
+
"""An unsaved instance has no related rows; value_from_object should
|
|
119
|
+
return an empty list rather than crash.
|
|
120
|
+
"""
|
|
121
|
+
widget = Widget(name="Mazda", size="3")
|
|
122
|
+
field = cast(ManyToManyField, Widget._model_meta.get_forward_field("tags"))
|
|
123
|
+
assert list(field.value_from_object(widget)) == []
|
|
124
|
+
|
|
125
|
+
|
|
97
126
|
def test_many_to_many_through_model(db):
|
|
98
127
|
"""Test accessing the through model directly."""
|
|
99
128
|
widget = Widget.query.create(name="Ford", size="Mustang")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from app.examples.models.iteration import IterationExample
|
|
4
|
+
|
|
5
|
+
from plain.postgres.db import get_connection
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _capture_queries(callable_):
|
|
9
|
+
conn = get_connection()
|
|
10
|
+
prev_force = conn.force_debug_cursor
|
|
11
|
+
conn.force_debug_cursor = True
|
|
12
|
+
conn.queries_log.clear()
|
|
13
|
+
try:
|
|
14
|
+
callable_()
|
|
15
|
+
return list(conn.queries_log)
|
|
16
|
+
finally:
|
|
17
|
+
conn.force_debug_cursor = prev_force
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_repr_does_not_execute_sql_when_unevaluated(db):
|
|
21
|
+
"""repr() of an unevaluated queryset must not issue a SQL query.
|
|
22
|
+
|
|
23
|
+
Error reporters (Sentry, pdb, exception templates) call repr() on
|
|
24
|
+
stack-frame locals; a surprise SELECT inside an exception path can
|
|
25
|
+
overload the database.
|
|
26
|
+
"""
|
|
27
|
+
qs = IterationExample.query.all()
|
|
28
|
+
|
|
29
|
+
queries = _capture_queries(lambda: repr(qs))
|
|
30
|
+
|
|
31
|
+
assert queries == []
|
|
32
|
+
assert repr(qs) == "<QuerySet [unevaluated]>"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_repr_uses_cache_when_evaluated(db):
|
|
36
|
+
"""Once a queryset is evaluated, repr() reflects its rows without re-querying."""
|
|
37
|
+
IterationExample.query.create(name="alpha", tag="a")
|
|
38
|
+
IterationExample.query.create(name="beta", tag="b")
|
|
39
|
+
|
|
40
|
+
qs = IterationExample.query.all()
|
|
41
|
+
list(qs) # force evaluation, populates _result_cache
|
|
42
|
+
|
|
43
|
+
queries = _capture_queries(lambda: repr(qs))
|
|
44
|
+
|
|
45
|
+
assert queries == []
|
|
46
|
+
rendered = repr(qs)
|
|
47
|
+
assert "[unevaluated]" not in rendered
|
|
48
|
+
assert rendered.count("IterationExample") == 2
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_repr_truncates_large_evaluated_querysets(db):
|
|
52
|
+
"""The truncation marker still appears past REPR_OUTPUT_SIZE."""
|
|
53
|
+
for i in range(25):
|
|
54
|
+
IterationExample.query.create(name=f"name{i:02d}", tag="t")
|
|
55
|
+
|
|
56
|
+
qs = IterationExample.query.all()
|
|
57
|
+
list(qs)
|
|
58
|
+
|
|
59
|
+
rendered = repr(qs)
|
|
60
|
+
assert "remaining elements truncated" in rendered
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/related_descriptors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/fields/reverse_descriptors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/context.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/helpers.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/ownership.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/runner.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/introspection/health/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/__init__.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/base.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/fields.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/models.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/plain/postgres/migrations/operations/special.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0006_secretstore.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0010_hideableitem.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/migrations/0016_formsexample.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/relationships.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/app/examples/models/storage_parameters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_autodetector_type_change.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_connection_isolation.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_connection_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_constraint_violation_error.py
RENAMED
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_constraints.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_defaults.py
RENAMED
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_indexes.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_nullability.py
RENAMED
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_convergence_timeouts.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_db_expression_defaults.py
RENAMED
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_executor_connection_hook.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_literal_default_persistence.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_management_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_no_callable_defaults.py
RENAMED
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_preflight_fk_coverage.py
RENAMED
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/internal/test_schema_normalize_type.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_postgres-0.103.2 → plain_postgres-0.103.3}/tests/public/test_read_only_transactions.py
RENAMED
|
File without changes
|
|
File without changes
|