django-cte 1.3.3.dev20250526204410__tar.gz → 1.3.3.dev20250530125340__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cte
3
- Version: 1.3.3.dev20250526204410
3
+ Version: 1.3.3.dev20250530125340
4
4
  Summary: Common Table Expressions (CTE) for Django
5
5
  Author-email: Daniel Miller <millerdev@gmail.com>
6
6
  Requires-Python: >= 3.9
@@ -18,12 +18,13 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Framework :: Django
22
23
  Classifier: Framework :: Django :: 4
23
24
  Classifier: Framework :: Django :: 4.2
24
25
  Classifier: Framework :: Django :: 5
25
- Classifier: Framework :: Django :: 5.0
26
26
  Classifier: Framework :: Django :: 5.1
27
+ Classifier: Framework :: Django :: 5.2
27
28
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
29
  License-File: LICENSE
29
30
  Requires-Dist: django
@@ -1,3 +1,3 @@
1
1
  from .cte import CTEManager, CTEQuerySet, With # noqa
2
2
 
3
- __version__ = "1.3.3.dev20250526204410"
3
+ __version__ = "1.3.3.dev20250530125340"
@@ -1,4 +1,5 @@
1
1
  from django.db.models import Manager
2
+ from django.db.models.expressions import Ref
2
3
  from django.db.models.query import Q, QuerySet, ValuesIterable
3
4
  from django.db.models.sql.datastructures import BaseTable
4
5
 
@@ -107,19 +108,27 @@ class With(object):
107
108
  query.join(BaseTable(self.name, None))
108
109
  query.default_cols = cte_query.default_cols
109
110
  query.deferred_loading = cte_query.deferred_loading
111
+ if cte_query.values_select:
112
+ query.set_values(cte_query.values_select)
113
+ qs._iterable_class = ValuesIterable
114
+ for alias in getattr(cte_query, "selected", None) or ():
115
+ if alias not in cte_query.annotations:
116
+ field = cte_query.resolve_ref(alias).output_field
117
+ col = CTEColumnRef(alias, self.name, field)
118
+ query.add_annotation(col, alias)
110
119
  if cte_query.annotations:
111
120
  for alias, value in cte_query.annotations.items():
112
121
  col = CTEColumnRef(alias, self.name, value.output_field)
113
122
  query.add_annotation(col, alias)
114
- if cte_query.values_select:
115
- query.set_values(cte_query.values_select)
116
- qs._iterable_class = ValuesIterable
117
123
  query.annotation_select_mask = cte_query.annotation_select_mask
118
124
 
119
125
  qs.query = query
120
126
  return qs
121
127
 
122
128
  def _resolve_ref(self, name):
129
+ selected = getattr(self.query, "selected", None)
130
+ if selected and name in selected and name not in self.query.annotations:
131
+ return Ref(name, self.query)
123
132
  return self.query.resolve_ref(name)
124
133
 
125
134
 
@@ -153,6 +162,35 @@ class CTEQuerySet(QuerySet):
153
162
  as_manager.queryset_only = True
154
163
  as_manager = classmethod(as_manager)
155
164
 
165
+ def _combinator_query(self, *args, **kw):
166
+ clone = super()._combinator_query(*args, **kw)
167
+ if clone.query.combinator:
168
+ ctes = clone.query._with_ctes = []
169
+ seen = {}
170
+ for query in clone.query.combined_queries:
171
+ for cte in getattr(query, "_with_ctes", []):
172
+ if seen.get(cte.name) is cte:
173
+ continue
174
+ if cte.name in seen:
175
+ raise ValueError(
176
+ f"Found two or more CTEs named '{cte.name}'. "
177
+ "Hint: assign a unique name to each CTE."
178
+ )
179
+ ctes.append(cte)
180
+ seen[cte.name] = cte
181
+ if ctes:
182
+ def without_ctes(query):
183
+ if getattr(query, "_with_ctes", None):
184
+ query = query.clone()
185
+ query._with_ctes = []
186
+ return query
187
+
188
+ clone.query.combined_queries = [
189
+ without_ctes(query)
190
+ for query in clone.query.combined_queries
191
+ ]
192
+ return clone
193
+
156
194
 
157
195
  class CTEManager(Manager.from_queryset(CTEQuerySet)):
158
196
  """Manager for models that perform CTE queries"""
@@ -1,4 +1,3 @@
1
- import django
2
1
  from django.db.models import Subquery
3
2
 
4
3
 
@@ -31,12 +30,8 @@ class CTESubqueryResolver(object):
31
30
 
32
31
  # --- end copied code --- #
33
32
 
34
- if django.VERSION < (3, 0):
35
- def get_query(clone):
36
- return clone.queryset.query
37
- else:
38
- def get_query(clone):
39
- return clone.query
33
+ def get_query(clone):
34
+ return clone.query
40
35
 
41
36
  # NOTE this uses the old (pre-Django 3) way of resolving.
42
37
  # Should a different technique should be used on Django 3+?
@@ -8,7 +8,6 @@ from django.db.models.sql.compiler import (
8
8
  SQLUpdateCompiler,
9
9
  )
10
10
  from django.db.models.sql.constants import LOUTER
11
- from django.db.models.sql.where import ExtraWhere, WhereNode
12
11
 
13
12
  from .expressions import CTESubqueryResolver
14
13
  from .join import QJoin
@@ -55,13 +54,8 @@ class CTEQuery(Query):
55
54
  clone._with_ctes = self._with_ctes[:]
56
55
  return clone
57
56
 
58
- if django.VERSION < (2, 0):
59
- def clone(self, klass=None, *args, **kwargs):
60
- return self.__chain("clone", klass, *args, **kwargs)
61
-
62
- else:
63
- def chain(self, klass=None):
64
- return self.__chain("chain", klass)
57
+ def chain(self, klass=None):
58
+ return self.__chain("chain", klass)
65
59
 
66
60
 
67
61
  class CTECompiler(object):
@@ -82,49 +76,27 @@ class CTECompiler(object):
82
76
  not isinstance(alias, QJoin) or alias.join_type != LOUTER
83
77
  )
84
78
 
85
- if django.VERSION >= (4, 0):
86
- compiler = cte.query.get_compiler(
87
- connection=connection, elide_empty=should_elide_empty
88
- )
89
- else:
90
- compiler = cte.query.get_compiler(connection=connection)
79
+ compiler = cte.query.get_compiler(
80
+ connection=connection, elide_empty=should_elide_empty
81
+ )
91
82
 
92
83
  qn = compiler.quote_name_unless_alias
93
84
  try:
94
85
  cte_sql, cte_params = compiler.as_sql()
95
86
  except EmptyResultSet:
96
- if django.VERSION < (4, 0) and not should_elide_empty:
97
- # elide_empty is not available prior to Django 4.0. The
98
- # below behavior emulates the logic of it, rebuilding
99
- # the CTE query with a WHERE clause that is always false
100
- # but that the SqlCompiler cannot optimize away. This is
101
- # only required for left outer joins, as standard inner
102
- # joins should be optimized and raise the EmptyResultSet
103
- query = cte.query.copy()
104
- query.where = WhereNode([ExtraWhere(["1 = 0"], [])])
105
- compiler = query.get_compiler(connection=connection)
106
- cte_sql, cte_params = compiler.as_sql()
107
- else:
108
- # If the CTE raises an EmptyResultSet the SqlCompiler still
109
- # needs to know the information about this base compiler
110
- # like, col_count and klass_info.
111
- as_sql()
112
- raise
87
+ # If the CTE raises an EmptyResultSet the SqlCompiler still
88
+ # needs to know the information about this base compiler
89
+ # like, col_count and klass_info.
90
+ as_sql()
91
+ raise
113
92
  template = cls.get_cte_query_template(cte)
114
93
  ctes.append(template.format(name=qn(cte.name), query=cte_sql))
115
94
  params.extend(cte_params)
116
95
 
117
- # Required due to breaking change in django commit
118
- # fc91ea1e50e5ef207f0f291b3f6c1942b10db7c7
119
- if django.VERSION >= (4, 0):
120
- explain_attribute = "explain_info"
121
- explain_info = getattr(query, explain_attribute, None)
122
- explain_format = getattr(explain_info, "format", None)
123
- explain_options = getattr(explain_info, "options", {})
124
- else:
125
- explain_attribute = "explain_query"
126
- explain_format = getattr(query, "explain_format", None)
127
- explain_options = getattr(query, "explain_options", {})
96
+ explain_attribute = "explain_info"
97
+ explain_info = getattr(query, explain_attribute, None)
98
+ explain_format = getattr(explain_info, "format", None)
99
+ explain_options = getattr(explain_info, "options", {})
128
100
 
129
101
  explain_query_or_info = getattr(query, explain_attribute, None)
130
102
  sql = []
@@ -6,6 +6,8 @@ license = {file = "LICENSE"}
6
6
  readme = {file = "README.md", content-type = "text/markdown"}
7
7
  dynamic = ["version"]
8
8
  requires-python = ">= 3.9"
9
+ # Python and Django versions are read from this file by GitHub Actions.
10
+ # Precise formatting is important.
9
11
  classifiers = [
10
12
  "Development Status :: 5 - Production/Stable",
11
13
  'Environment :: Web Environment',
@@ -20,12 +22,13 @@ classifiers = [
20
22
  'Programming Language :: Python :: 3.11',
21
23
  'Programming Language :: Python :: 3.12',
22
24
  'Programming Language :: Python :: 3.13',
25
+ 'Programming Language :: Python :: 3.14',
23
26
  'Framework :: Django',
24
27
  'Framework :: Django :: 4',
25
28
  'Framework :: Django :: 4.2',
26
29
  'Framework :: Django :: 5',
27
- 'Framework :: Django :: 5.0',
28
30
  'Framework :: Django :: 5.1',
31
+ 'Framework :: Django :: 5.2',
29
32
  'Topic :: Software Development :: Libraries :: Python Modules',
30
33
  ]
31
34
  dependencies = ["django"]