plain.models 0.40.1__tar.gz → 0.41.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 (132) hide show
  1. {plain_models-0.40.1 → plain_models-0.41.0}/PKG-INFO +2 -2
  2. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/CHANGELOG.md +14 -0
  3. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/operations/models.py +0 -1
  4. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/options.py +0 -2
  5. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/otel.py +50 -0
  6. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/query.py +2 -57
  7. {plain_models-0.40.1 → plain_models-0.41.0}/pyproject.toml +2 -2
  8. {plain_models-0.40.1 → plain_models-0.41.0}/.gitignore +0 -0
  9. {plain_models-0.40.1 → plain_models-0.41.0}/LICENSE +0 -0
  10. {plain_models-0.40.1 → plain_models-0.41.0}/README.md +0 -0
  11. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/AGENTS.md +0 -0
  12. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/README.md +0 -0
  13. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/__init__.py +0 -0
  14. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/aggregates.py +0 -0
  15. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/__init__.py +0 -0
  16. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/__init__.py +0 -0
  17. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/base.py +0 -0
  18. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/client.py +0 -0
  19. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/creation.py +0 -0
  20. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/features.py +0 -0
  21. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/introspection.py +0 -0
  22. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/operations.py +0 -0
  23. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/schema.py +0 -0
  24. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/base/validation.py +0 -0
  25. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/ddl_references.py +0 -0
  26. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/__init__.py +0 -0
  27. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/base.py +0 -0
  28. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/client.py +0 -0
  29. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/compiler.py +0 -0
  30. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/creation.py +0 -0
  31. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/features.py +0 -0
  32. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/introspection.py +0 -0
  33. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/operations.py +0 -0
  34. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/schema.py +0 -0
  35. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/mysql/validation.py +0 -0
  36. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/postgresql/__init__.py +0 -0
  37. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/postgresql/base.py +0 -0
  38. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/postgresql/client.py +0 -0
  39. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/postgresql/creation.py +0 -0
  40. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/postgresql/features.py +0 -0
  41. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/postgresql/introspection.py +0 -0
  42. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/postgresql/operations.py +0 -0
  43. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/postgresql/schema.py +0 -0
  44. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/__init__.py +0 -0
  45. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/_functions.py +0 -0
  46. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/base.py +0 -0
  47. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/client.py +0 -0
  48. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/creation.py +0 -0
  49. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/features.py +0 -0
  50. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/introspection.py +0 -0
  51. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/operations.py +0 -0
  52. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/sqlite3/schema.py +0 -0
  53. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backends/utils.py +0 -0
  54. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backups/__init__.py +0 -0
  55. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backups/cli.py +0 -0
  56. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backups/clients.py +0 -0
  57. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/backups/core.py +0 -0
  58. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/base.py +0 -0
  59. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/cli.py +0 -0
  60. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/config.py +0 -0
  61. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/connections.py +0 -0
  62. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/constants.py +0 -0
  63. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/constraints.py +0 -0
  64. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/database_url.py +0 -0
  65. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/db.py +0 -0
  66. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/default_settings.py +0 -0
  67. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/deletion.py +0 -0
  68. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/entrypoints.py +0 -0
  69. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/enums.py +0 -0
  70. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/exceptions.py +0 -0
  71. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/expressions.py +0 -0
  72. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/fields/__init__.py +0 -0
  73. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/fields/json.py +0 -0
  74. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/fields/mixins.py +0 -0
  75. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/fields/related.py +0 -0
  76. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/fields/related_descriptors.py +0 -0
  77. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/fields/related_lookups.py +0 -0
  78. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/fields/reverse_related.py +0 -0
  79. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/forms.py +0 -0
  80. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/functions/__init__.py +0 -0
  81. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/functions/comparison.py +0 -0
  82. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/functions/datetime.py +0 -0
  83. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/functions/math.py +0 -0
  84. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/functions/mixins.py +0 -0
  85. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/functions/text.py +0 -0
  86. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/functions/window.py +0 -0
  87. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/indexes.py +0 -0
  88. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/lookups.py +0 -0
  89. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/manager.py +0 -0
  90. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/__init__.py +0 -0
  91. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/autodetector.py +0 -0
  92. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/exceptions.py +0 -0
  93. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/executor.py +0 -0
  94. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/graph.py +0 -0
  95. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/loader.py +0 -0
  96. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/migration.py +0 -0
  97. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/operations/__init__.py +0 -0
  98. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/operations/base.py +0 -0
  99. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/operations/fields.py +0 -0
  100. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/operations/special.py +0 -0
  101. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/optimizer.py +0 -0
  102. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/questioner.py +0 -0
  103. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/recorder.py +0 -0
  104. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/serializer.py +0 -0
  105. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/state.py +0 -0
  106. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/utils.py +0 -0
  107. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/migrations/writer.py +0 -0
  108. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/preflight.py +0 -0
  109. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/query_utils.py +0 -0
  110. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/registry.py +0 -0
  111. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/sql/__init__.py +0 -0
  112. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/sql/compiler.py +0 -0
  113. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/sql/constants.py +0 -0
  114. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/sql/datastructures.py +0 -0
  115. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/sql/query.py +0 -0
  116. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/sql/subqueries.py +0 -0
  117. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/sql/where.py +0 -0
  118. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/test/__init__.py +0 -0
  119. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/test/pytest.py +0 -0
  120. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/test/utils.py +0 -0
  121. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/transaction.py +0 -0
  122. {plain_models-0.40.1 → plain_models-0.41.0}/plain/models/utils.py +0 -0
  123. {plain_models-0.40.1 → plain_models-0.41.0}/tests/app/examples/migrations/0001_initial.py +0 -0
  124. {plain_models-0.40.1 → plain_models-0.41.0}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
  125. {plain_models-0.40.1 → plain_models-0.41.0}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
  126. {plain_models-0.40.1 → plain_models-0.41.0}/tests/app/examples/migrations/__init__.py +0 -0
  127. {plain_models-0.40.1 → plain_models-0.41.0}/tests/app/examples/models.py +0 -0
  128. {plain_models-0.40.1 → plain_models-0.41.0}/tests/app/settings.py +0 -0
  129. {plain_models-0.40.1 → plain_models-0.41.0}/tests/app/urls.py +0 -0
  130. {plain_models-0.40.1 → plain_models-0.41.0}/tests/test_database_url.py +0 -0
  131. {plain_models-0.40.1 → plain_models-0.41.0}/tests/test_delete_behaviors.py +0 -0
  132. {plain_models-0.40.1 → plain_models-0.41.0}/tests/test_models.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.models
3
- Version: 0.40.1
3
+ Version: 0.41.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-File: LICENSE
7
- Requires-Python: >=3.11
7
+ Requires-Python: >=3.13
8
8
  Requires-Dist: plain<1.0.0
9
9
  Requires-Dist: sqlparse>=0.3.1
10
10
  Description-Content-Type: text/markdown
@@ -1,5 +1,19 @@
1
1
  # plain-models changelog
2
2
 
3
+ ## [0.41.0](https://github.com/dropseed/plain/releases/plain-models@0.41.0) (2025-09-09)
4
+
5
+ ### What's changed
6
+
7
+ - Python 3.13 is now the minimum required version ([d86e307](https://github.com/dropseed/plain/commit/d86e307))
8
+ - Removed the `earliest()`, `latest()`, and `get_latest_by` model meta option - use `order_by().first()` and `order_by().last()` instead ([b6093a8](https://github.com/dropseed/plain/commit/b6093a8))
9
+ - Removed automatic ordering in `first()` and `last()` queryset methods - they now respect the existing queryset ordering without adding default ordering ([adc19a6](https://github.com/dropseed/plain/commit/adc19a6))
10
+ - Added code location attributes to database operation tracing, showing the source file, line number, and function where the query originated ([da36a17](https://github.com/dropseed/plain/commit/da36a17))
11
+
12
+ ### Upgrade instructions
13
+
14
+ - Replace usage of `earliest()`, `latest()`, and model `Meta` `get_latest_by` queryset methods with equivalent `order_by().first()` or `order_by().last()` calls
15
+ - The `first()` and `last()` methods no longer automatically add ordering by `id` - explicitly add `.order_by()` to your querysets or `ordering` to your models `Meta` class if needed
16
+
3
17
  ## [0.40.1](https://github.com/dropseed/plain/releases/plain-models@0.40.1) (2025-09-03)
4
18
 
5
19
  ### What's changed
@@ -450,7 +450,6 @@ class AlterModelOptions(ModelOptionOperation):
450
450
  "base_manager_name",
451
451
  "default_manager_name",
452
452
  "default_related_name",
453
- "get_latest_by",
454
453
  "ordering",
455
454
  ]
456
455
 
@@ -25,7 +25,6 @@ DEFAULT_NAMES = (
25
25
  "db_table",
26
26
  "db_table_comment",
27
27
  "ordering",
28
- "get_latest_by",
29
28
  "package_label",
30
29
  "models_registry",
31
30
  "default_related_name",
@@ -74,7 +73,6 @@ class Options:
74
73
  self.constraints = []
75
74
  self.object_name = None
76
75
  self.package_label = package_label
77
- self.get_latest_by = None
78
76
  self.required_db_features = []
79
77
  self.required_db_vendor = None
80
78
  self.meta = meta
@@ -1,4 +1,5 @@
1
1
  import re
2
+ import traceback
2
3
  from contextlib import contextmanager
3
4
  from typing import Any
4
5
 
@@ -8,6 +9,13 @@ from opentelemetry.semconv._incubating.attributes.db_attributes import (
8
9
  DB_QUERY_PARAMETER_TEMPLATE,
9
10
  DB_USER,
10
11
  )
12
+ from opentelemetry.semconv.attributes.code_attributes import (
13
+ CODE_COLUMN_NUMBER,
14
+ CODE_FILE_PATH,
15
+ CODE_FUNCTION_NAME,
16
+ CODE_LINE_NUMBER,
17
+ CODE_STACKTRACE,
18
+ )
11
19
  from opentelemetry.semconv.attributes.db_attributes import (
12
20
  DB_COLLECTION_NAME,
13
21
  DB_NAMESPACE,
@@ -126,6 +134,8 @@ def db_span(db, sql: Any, *, many: bool = False, params=None):
126
134
  DB_OPERATION_NAME: operation,
127
135
  }
128
136
 
137
+ attrs.update(_get_code_attributes())
138
+
129
139
  # Add collection name if detected
130
140
  if collection_name:
131
141
  attrs[DB_COLLECTION_NAME] = collection_name
@@ -173,3 +183,43 @@ def suppress_db_tracing():
173
183
  yield
174
184
  finally:
175
185
  otel_context.detach(token)
186
+
187
+
188
+ def _get_code_attributes():
189
+ """Extract code context attributes for the current database query.
190
+
191
+ Returns a dict of OpenTelemetry code attributes.
192
+ """
193
+ stack = traceback.extract_stack()
194
+
195
+ # Find the user code frame
196
+ for frame in reversed(stack):
197
+ filepath = frame.filename
198
+ if not filepath:
199
+ continue
200
+
201
+ if "/plain/models/" in filepath:
202
+ continue
203
+
204
+ if filepath.endswith("contextlib.py"):
205
+ continue
206
+
207
+ # Found user code - build attributes dict
208
+ attrs = {}
209
+
210
+ if filepath:
211
+ attrs[CODE_FILE_PATH] = filepath
212
+ if frame.lineno:
213
+ attrs[CODE_LINE_NUMBER] = frame.lineno
214
+ if frame.name:
215
+ attrs[CODE_FUNCTION_NAME] = frame.name
216
+ if frame.colno:
217
+ attrs[CODE_COLUMN_NUMBER] = frame.colno
218
+
219
+ # Add full stack trace only in DEBUG mode (expensive)
220
+ if settings.DEBUG:
221
+ attrs[CODE_STACKTRACE] = "".join(traceback.format_stack())
222
+
223
+ return attrs
224
+
225
+ return {}
@@ -873,59 +873,14 @@ class QuerySet:
873
873
  )
874
874
  return params
875
875
 
876
- def _earliest(self, *fields):
877
- """
878
- Return the earliest object according to fields (if given) or by the
879
- model's Meta.get_latest_by.
880
- """
881
- if fields:
882
- order_by = fields
883
- else:
884
- order_by = getattr(self.model._meta, "get_latest_by")
885
- if order_by and not isinstance(order_by, tuple | list):
886
- order_by = (order_by,)
887
- if order_by is None:
888
- raise ValueError(
889
- "earliest() and latest() require either fields as positional "
890
- "arguments or 'get_latest_by' in the model's Meta."
891
- )
892
- obj = self._chain()
893
- obj.query.set_limits(high=1)
894
- obj.query.clear_ordering(force=True)
895
- obj.query.add_ordering(*order_by)
896
- return obj.get()
897
-
898
- def earliest(self, *fields):
899
- if self.query.is_sliced:
900
- raise TypeError("Cannot change a query once a slice has been taken.")
901
- return self._earliest(*fields)
902
-
903
- def latest(self, *fields):
904
- """
905
- Return the latest object according to fields (if given) or by the
906
- model's Meta.get_latest_by.
907
- """
908
- if self.query.is_sliced:
909
- raise TypeError("Cannot change a query once a slice has been taken.")
910
- return self.reverse()._earliest(*fields)
911
-
912
876
  def first(self):
913
877
  """Return the first object of a query or None if no match is found."""
914
- if self.ordered:
915
- queryset = self
916
- else:
917
- self._check_ordering_first_last_queryset_aggregation(method="first")
918
- queryset = self.order_by("id")
919
- for obj in queryset[:1]:
878
+ for obj in self[:1]:
920
879
  return obj
921
880
 
922
881
  def last(self):
923
882
  """Return the last object of a query or None if no match is found."""
924
- if self.ordered:
925
- queryset = self.reverse()
926
- else:
927
- self._check_ordering_first_last_queryset_aggregation(method="last")
928
- queryset = self.order_by("-id")
883
+ queryset = self.reverse()
929
884
  for obj in queryset[:1]:
930
885
  return obj
931
886
 
@@ -1763,16 +1718,6 @@ class QuerySet:
1763
1718
  if self.query.combinator or other.query.combinator:
1764
1719
  raise TypeError(f"Cannot use {operator_} operator with combined queryset.")
1765
1720
 
1766
- def _check_ordering_first_last_queryset_aggregation(self, method):
1767
- if isinstance(self.query.group_by, tuple) and not any(
1768
- col.output_field is self.model._meta.get_field("id")
1769
- for col in self.query.group_by
1770
- ):
1771
- raise TypeError(
1772
- f"Cannot use QuerySet.{method}() on an unordered queryset performing "
1773
- f"aggregation. Add an ordering with order_by()."
1774
- )
1775
-
1776
1721
 
1777
1722
  class InstanceCheckMeta(type):
1778
1723
  def __instancecheck__(self, instance):
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "plain.models"
3
- version = "0.40.1"
3
+ version = "0.41.0"
4
4
  description = "Model your data and store it in a database."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  readme = "README.md"
7
- requires-python = ">=3.11"
7
+ requires-python = ">=3.13"
8
8
  dependencies = [
9
9
  "plain<1.0.0",
10
10
  "sqlparse>=0.3.1",
File without changes
File without changes
File without changes