relationalai 1.0.0a3__py3-none-any.whl → 1.0.0a5__py3-none-any.whl

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 (118) hide show
  1. relationalai/config/config.py +47 -21
  2. relationalai/config/connections/__init__.py +5 -2
  3. relationalai/config/connections/duckdb.py +2 -2
  4. relationalai/config/connections/local.py +31 -0
  5. relationalai/config/connections/snowflake.py +0 -1
  6. relationalai/config/external/raiconfig_converter.py +235 -0
  7. relationalai/config/external/raiconfig_models.py +202 -0
  8. relationalai/config/external/utils.py +31 -0
  9. relationalai/config/shims.py +1 -0
  10. relationalai/semantics/__init__.py +10 -8
  11. relationalai/semantics/backends/sql/sql_compiler.py +1 -4
  12. relationalai/semantics/experimental/__init__.py +0 -0
  13. relationalai/semantics/experimental/builder.py +295 -0
  14. relationalai/semantics/experimental/builtins.py +154 -0
  15. relationalai/semantics/frontend/base.py +67 -42
  16. relationalai/semantics/frontend/core.py +34 -6
  17. relationalai/semantics/frontend/front_compiler.py +209 -37
  18. relationalai/semantics/frontend/pprint.py +6 -2
  19. relationalai/semantics/metamodel/__init__.py +7 -0
  20. relationalai/semantics/metamodel/metamodel.py +2 -0
  21. relationalai/semantics/metamodel/metamodel_analyzer.py +58 -16
  22. relationalai/semantics/metamodel/pprint.py +6 -1
  23. relationalai/semantics/metamodel/rewriter.py +11 -7
  24. relationalai/semantics/metamodel/typer.py +116 -41
  25. relationalai/semantics/reasoners/__init__.py +11 -0
  26. relationalai/semantics/reasoners/graph/__init__.py +35 -0
  27. relationalai/semantics/reasoners/graph/core.py +9028 -0
  28. relationalai/semantics/std/__init__.py +30 -10
  29. relationalai/semantics/std/aggregates.py +641 -12
  30. relationalai/semantics/std/common.py +146 -13
  31. relationalai/semantics/std/constraints.py +71 -1
  32. relationalai/semantics/std/datetime.py +904 -21
  33. relationalai/semantics/std/decimals.py +143 -2
  34. relationalai/semantics/std/floats.py +57 -4
  35. relationalai/semantics/std/integers.py +98 -4
  36. relationalai/semantics/std/math.py +857 -35
  37. relationalai/semantics/std/numbers.py +216 -20
  38. relationalai/semantics/std/re.py +213 -5
  39. relationalai/semantics/std/strings.py +437 -44
  40. relationalai/shims/executor.py +60 -52
  41. relationalai/shims/fixtures.py +85 -0
  42. relationalai/shims/helpers.py +26 -2
  43. relationalai/shims/hoister.py +28 -9
  44. relationalai/shims/mm2v0.py +204 -173
  45. relationalai/tools/cli/cli.py +192 -10
  46. relationalai/tools/cli/components/progress_reader.py +1 -1
  47. relationalai/tools/cli/docs.py +394 -0
  48. relationalai/tools/debugger.py +11 -4
  49. relationalai/tools/qb_debugger.py +435 -0
  50. relationalai/tools/typer_debugger.py +1 -2
  51. relationalai/util/dataclasses.py +3 -5
  52. relationalai/util/docutils.py +1 -2
  53. relationalai/util/error.py +2 -5
  54. relationalai/util/python.py +23 -0
  55. relationalai/util/runtime.py +1 -2
  56. relationalai/util/schema.py +2 -4
  57. relationalai/util/structures.py +4 -2
  58. relationalai/util/tracing.py +8 -2
  59. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/METADATA +8 -5
  60. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/RECORD +118 -95
  61. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/WHEEL +1 -1
  62. v0/relationalai/__init__.py +1 -1
  63. v0/relationalai/clients/client.py +52 -18
  64. v0/relationalai/clients/exec_txn_poller.py +122 -0
  65. v0/relationalai/clients/local.py +23 -8
  66. v0/relationalai/clients/resources/azure/azure.py +36 -11
  67. v0/relationalai/clients/resources/snowflake/__init__.py +4 -4
  68. v0/relationalai/clients/resources/snowflake/cli_resources.py +12 -1
  69. v0/relationalai/clients/resources/snowflake/direct_access_resources.py +124 -100
  70. v0/relationalai/clients/resources/snowflake/engine_service.py +381 -0
  71. v0/relationalai/clients/resources/snowflake/engine_state_handlers.py +35 -29
  72. v0/relationalai/clients/resources/snowflake/error_handlers.py +43 -2
  73. v0/relationalai/clients/resources/snowflake/snowflake.py +277 -179
  74. v0/relationalai/clients/resources/snowflake/use_index_poller.py +8 -0
  75. v0/relationalai/clients/types.py +5 -0
  76. v0/relationalai/errors.py +19 -1
  77. v0/relationalai/semantics/lqp/algorithms.py +173 -0
  78. v0/relationalai/semantics/lqp/builtins.py +199 -2
  79. v0/relationalai/semantics/lqp/executor.py +68 -37
  80. v0/relationalai/semantics/lqp/ir.py +28 -2
  81. v0/relationalai/semantics/lqp/model2lqp.py +215 -45
  82. v0/relationalai/semantics/lqp/passes.py +13 -658
  83. v0/relationalai/semantics/lqp/rewrite/__init__.py +12 -0
  84. v0/relationalai/semantics/lqp/rewrite/algorithm.py +385 -0
  85. v0/relationalai/semantics/lqp/rewrite/constants_to_vars.py +70 -0
  86. v0/relationalai/semantics/lqp/rewrite/deduplicate_vars.py +104 -0
  87. v0/relationalai/semantics/lqp/rewrite/eliminate_data.py +108 -0
  88. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +25 -3
  89. v0/relationalai/semantics/lqp/rewrite/period_math.py +77 -0
  90. v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +65 -31
  91. v0/relationalai/semantics/lqp/rewrite/unify_definitions.py +317 -0
  92. v0/relationalai/semantics/lqp/utils.py +11 -1
  93. v0/relationalai/semantics/lqp/validators.py +14 -1
  94. v0/relationalai/semantics/metamodel/builtins.py +2 -1
  95. v0/relationalai/semantics/metamodel/compiler.py +2 -1
  96. v0/relationalai/semantics/metamodel/dependency.py +12 -3
  97. v0/relationalai/semantics/metamodel/executor.py +11 -1
  98. v0/relationalai/semantics/metamodel/factory.py +2 -2
  99. v0/relationalai/semantics/metamodel/helpers.py +7 -0
  100. v0/relationalai/semantics/metamodel/ir.py +3 -2
  101. v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +30 -20
  102. v0/relationalai/semantics/metamodel/rewrite/flatten.py +50 -13
  103. v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +9 -3
  104. v0/relationalai/semantics/metamodel/typer/checker.py +6 -4
  105. v0/relationalai/semantics/metamodel/typer/typer.py +4 -3
  106. v0/relationalai/semantics/metamodel/visitor.py +4 -3
  107. v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +1 -1
  108. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +336 -86
  109. v0/relationalai/semantics/rel/compiler.py +2 -1
  110. v0/relationalai/semantics/rel/executor.py +3 -2
  111. v0/relationalai/semantics/tests/lqp/__init__.py +0 -0
  112. v0/relationalai/semantics/tests/lqp/algorithms.py +345 -0
  113. v0/relationalai/tools/cli.py +339 -186
  114. v0/relationalai/tools/cli_controls.py +216 -67
  115. v0/relationalai/tools/cli_helpers.py +410 -6
  116. v0/relationalai/util/format.py +5 -2
  117. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/entry_points.txt +0 -0
  118. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,19 @@
1
+ """
2
+ Aggregation functions for relational operations.
3
+
4
+ This module provides functions for aggregating data including:
5
+ - Statistical aggregates
6
+ - String aggregation
7
+ - Ranking and sorting
8
+ - Ordering modifiers
9
+ - Grouped aggregations
10
+ """
1
11
  from __future__ import annotations
2
- from ..frontend.base import Field, Library, Relationship, Aggregate, Group, TupleVariable, Value, Distinct
12
+ from ..frontend.base import Field, Library, Aggregate, Group, TupleVariable, Value, Distinct
3
13
  from ..frontend.core import Any, Boolean, Number, String, Integer, TypeVar, ScaledNumber, Numeric, Float
14
+ from relationalai.util.docutils import include_in_docs
15
+
16
+ __include_in_docs__ = True
4
17
 
5
18
  library = Library("aggregates")
6
19
 
@@ -12,38 +25,192 @@ AggValue = Value | Distinct
12
25
 
13
26
  _sum = library.Relation("sum", fields=[Field.input("value", Numeric), Field("result", Numeric)],
14
27
  overloads=[[Number, Number], [Float, Float]])
15
- _count = library.Relation("count", fields=[Field("result", Integer)])
16
- _avg = library.Relation("avg", fields=[Field.input("over", Numeric), Field("result", Numeric)],
17
- overloads=[[Number, ScaledNumber], [Float, Float]])
18
- _min = library.Relation("min", fields=[Field.input("over", TypeVar), Field("result", TypeVar)])
19
- _max = library.Relation("max", fields=[Field.input("over", TypeVar), Field("result", TypeVar)])
20
- _string_join = library.Relation("string_join",
21
- fields=[Field.input("index", Number), Field.input("sep", String), Field.input("over", String), Field("result", String)])
22
28
 
23
- _sort = library.Relation("sort", fields=[Field.input("limit", Integer), Field.input("args", Any, is_list=True), Field.input("is_asc", Boolean, is_list=True)])
24
- _rank = library.Relation("rank", fields=[Field.input("args", Any, is_list=True), Field.input("is_asc", Boolean, is_list=True), Field("rank", Integer)])
25
- _limit = library.Relation("limit", fields=[Field.input("limit", Integer), Field.input("args", Any, is_list=True), Field.input("is_asc", Boolean, is_list=True)])
29
+ @include_in_docs
30
+ def sum(*args: AggValue) -> Aggregate:
31
+ """
32
+ Compute the sum of values.
26
33
 
34
+ Parameters
35
+ ----------
36
+ *args: AggValue
37
+ Values to sum. Can include Distinct wrapper for distinct aggregation.
27
38
 
28
- def sum(*args: AggValue) -> Aggregate:
39
+ Returns
40
+ -------
41
+ Aggregate
42
+ An `Aggregate` representing the computation of the sum. Returns `Number` if the input is `Number`, or `Float` if the input is `Float`.
43
+
44
+ Examples
45
+ --------
46
+ Sum order amounts:
47
+
48
+ >>> select(aggregates.sum(Order.amount))
49
+
50
+ Sum employee salaries per department:
51
+
52
+ >>> select(Department, aggregates.sum(Employee.salary).per(Department))
53
+ """
29
54
  return Aggregate(_sum, *args)
30
55
 
56
+ _count = library.Relation("count", fields=[Field("result", Integer)])
57
+
58
+ @include_in_docs
31
59
  def count(*args: AggValue) -> Aggregate:
60
+ """
61
+ Count the number of values.
62
+
63
+ Parameters
64
+ ----------
65
+ *args: AggValue
66
+ Values to count. Can include Distinct wrapper for distinct count.
67
+
68
+ Returns
69
+ -------
70
+ Aggregate
71
+ An `Aggregate` representing the computation of the count. Returns `Integer`.
72
+
73
+ Examples
74
+ --------
75
+ Count all employees:
76
+
77
+ >>> select(aggregates.count(Employee))
78
+
79
+ Count employees per department:
80
+
81
+ >>> select(Department, aggregates.count(Employee).per(Department))
82
+ """
32
83
  return Aggregate(_count, *args)
33
84
 
85
+ _min = library.Relation("min", fields=[Field.input("over", TypeVar), Field("result", TypeVar)])
86
+
87
+ @include_in_docs
34
88
  def min(*args: AggValue) -> Aggregate:
89
+ """
90
+ Find the minimum value.
91
+
92
+ Parameters
93
+ ----------
94
+ *args: AggValue
95
+ Values to find minimum from.
96
+
97
+ Returns
98
+ -------
99
+ Aggregate
100
+ An `Aggregate` representing the computation of the minimum value. Returns the same type as the input.
101
+
102
+ Examples
103
+ --------
104
+ Find minimum salary:
105
+
106
+ >>> select(aggregates.min(Employee.salary))
107
+
108
+ Find minimum price per category:
109
+
110
+ >>> select(Category, aggregates.min(Product.price).per(Category).where(Product.category == Category))
111
+ """
35
112
  return Aggregate(_min, *args)
36
113
 
114
+ _max = library.Relation("max", fields=[Field.input("over", TypeVar), Field("result", TypeVar)])
115
+
116
+ @include_in_docs
37
117
  def max(*args: AggValue) -> Aggregate:
118
+ """
119
+ Find the maximum value.
120
+
121
+ Parameters
122
+ ----------
123
+ *args: AggValue
124
+ Values to find maximum from.
125
+
126
+ Returns
127
+ -------
128
+ Aggregate
129
+ An `Aggregate` representing the computation of the maximum value. Returns the same type as the input.
130
+
131
+ Examples
132
+ --------
133
+ Find maximum salary:
134
+
135
+ >>> select(aggregates.max(Employee.salary))
136
+
137
+ Find maximum order amount per customer:
138
+
139
+ >>> select(Customer, aggregates.max(Order.amount).per(Customer).where(Order.customer == Customer))
140
+ """
38
141
  return Aggregate(_max, *args)
39
142
 
143
+ _avg = library.Relation("avg", fields=[Field.input("over", Numeric), Field("result", Numeric)],
144
+ overloads=[[Number, ScaledNumber], [Float, Float]])
145
+
146
+ @include_in_docs
40
147
  def avg(*args: AggValue) -> Aggregate:
148
+ """
149
+ Compute the average of values.
150
+
151
+ Parameters
152
+ ----------
153
+ *args: AggValue
154
+ Values to average.
155
+
156
+ Returns
157
+ -------
158
+ Aggregate
159
+ An `Aggregate` representing the computation of the average. Returns `ScaledNumber` if the input is `Number`, or `Float` if the input is `Float`.
160
+
161
+ Examples
162
+ --------
163
+ Compute average salary:
164
+
165
+ >>> select(aggregates.avg(Employee.salary))
166
+
167
+ Compute average order amount per customer:
168
+
169
+ >>> select(Customer, aggregates.avg(Order.amount).per(Customer).where(Order.customer == Customer))
170
+ """
41
171
  return Aggregate(_avg, *args)
42
172
 
173
+ _string_join = library.Relation("string_join",
174
+ fields=[Field.input("index", Number), Field.input("sep", String), Field.input("over", String), Field("result", String)])
175
+
176
+ @include_in_docs
43
177
  def string_join(*args: AggValue, sep="", index=1) -> Aggregate:
178
+ """
179
+ Join string values with a separator.
180
+
181
+ Parameters
182
+ ----------
183
+ *args: AggValue
184
+ String values to join.
185
+ sep: str
186
+ Separator string to use between values. Default: empty string.
187
+ index: int
188
+ Index parameter for ordering. Default: 1.
189
+
190
+ Returns
191
+ -------
192
+ Aggregate
193
+ An `Aggregate` representing the computation of the joined string. Returns `String`.
194
+
195
+ Examples
196
+ --------
197
+ Join employee names with comma separator:
198
+
199
+ >>> select(aggregates.string_join(Employee.name, sep=", "))
200
+
201
+ Join product tags per category:
202
+
203
+ >>> select(Category, aggregates.string_join(Product.tag, sep="; ").per(Category).where(Product.category == Category))
204
+ """
44
205
  return Aggregate(_string_join, index, sep, *args)
45
206
 
207
+ _sort = library.Relation("sort", fields=[Field.input("limit", Integer), Field.input("args", Any, is_list=True), Field.input("is_asc", Boolean, is_list=True)])
208
+ _rank = library.Relation("rank", fields=[Field.input("args", Any, is_list=True), Field.input("is_asc", Boolean, is_list=True), Field("rank", Integer)])
209
+ _limit = library.Relation("limit", fields=[Field.input("limit", Integer), Field.input("args", Any, is_list=True), Field.input("is_asc", Boolean, is_list=True)])
210
+
211
+
46
212
  class Ordering:
213
+ """Helper class for specifying sort order in aggregations."""
47
214
  def __init__(self, *values:AggValue, is_asc=True) -> None:
48
215
  self._values = values
49
216
  self._is_asc = is_asc
@@ -72,77 +239,539 @@ class Ordering:
72
239
  has_distinct = Ordering.handle_arg(arg, ordering, ordering_args) or has_distinct
73
240
  return tuple(ordering_args), tuple(ordering), has_distinct
74
241
 
242
+ @include_in_docs
75
243
  def asc(*args: AggValue) -> Ordering:
244
+ """
245
+ Specify ascending order for values in sorting and ranking operations.
246
+
247
+ Parameters
248
+ ----------
249
+ *args: AggValue
250
+ Values to sort in ascending order.
251
+
252
+ Returns
253
+ -------
254
+ Ordering
255
+ An `Ordering` object representing ascending order.
256
+
257
+ Examples
258
+ --------
259
+ Rank employees by salary in ascending order:
260
+
261
+ >>> select(Employee, aggregates.rank(aggregates.asc(Employee.salary)))
262
+ """
76
263
  return Ordering(*args, is_asc=True)
77
264
 
265
+ @include_in_docs
78
266
  def desc(*args: AggValue) -> Ordering:
267
+ """
268
+ Specify descending order for values in sorting and ranking operations.
269
+
270
+ Parameters
271
+ ----------
272
+ *args: AggValue
273
+ Values to sort in descending order.
274
+
275
+ Returns
276
+ -------
277
+ Ordering
278
+ An `Ordering` object representing descending order.
279
+
280
+ Examples
281
+ --------
282
+ Rank employees by salary in descending order:
283
+
284
+ >>> select(Employee, aggregates.rank(aggregates.desc(Employee.salary)))
285
+ """
79
286
  return Ordering(*args, is_asc=False)
80
287
 
81
288
  def _sort_agg(limit: int, *args: AggValue|Ordering) -> Aggregate:
82
289
  ordering_args, is_asc, has_distinct = Ordering.get_ordering_args(args)
83
290
  return Aggregate(_sort, limit, TupleVariable(ordering_args), TupleVariable(is_asc), distinct=has_distinct)
84
291
 
292
+ @include_in_docs
85
293
  def rank(*args: AggValue|Ordering) -> Aggregate:
294
+ """
295
+ Compute rank based on the ordering of values.
296
+
297
+ Parameters
298
+ ----------
299
+ *args: AggValue | Ordering
300
+ Values or Ordering specifications to rank by.
301
+
302
+ Returns
303
+ -------
304
+ Aggregate
305
+ An `Aggregate` representing the computation of the rank. Returns `Integer`.
306
+
307
+ Examples
308
+ --------
309
+ Rank employees by salary:
310
+
311
+ >>> select(Employee, aggregates.rank(aggregates.desc(Employee.salary)))
312
+
313
+ Rank products by price per category:
314
+
315
+ >>> select(Product, aggregates.rank(aggregates.asc(Product.price)).per(Category).where(Product.category == Category))
316
+ """
86
317
  ordering_args, is_asc, has_distinct = Ordering.get_ordering_args(args)
87
318
  return Aggregate(_rank, TupleVariable(ordering_args), TupleVariable(is_asc), distinct=has_distinct)
88
319
 
320
+ @include_in_docs
89
321
  def limit(limit: int, *args: AggValue|Ordering) -> Aggregate:
322
+ """
323
+ Limit results based on ordering.
324
+
325
+ Parameters
326
+ ----------
327
+ limit: int
328
+ Maximum number of results to return.
329
+ *args: AggValue | Ordering
330
+ Values or Ordering specifications to order by.
331
+
332
+ Returns
333
+ -------
334
+ Aggregate
335
+ An `Aggregate` representing the computation of the limited results.
336
+
337
+ Examples
338
+ --------
339
+ Get top 5 employees by salary:
340
+
341
+ >>> select(Employee).where(aggregates.limit(5, aggregates.desc(Employee.salary)))
342
+
343
+ Get top 3 products by sales per category:
344
+
345
+ >>> select(Product).where(aggregates.limit(3, aggregates.desc(Product.sales)).per(Category).where(Product.category == Category))
346
+ """
90
347
  ordering_args, is_asc, has_distinct = Ordering.get_ordering_args(args)
91
348
  return Aggregate(_limit, limit, TupleVariable(ordering_args), TupleVariable(is_asc), distinct=has_distinct)
92
349
 
350
+ @include_in_docs
93
351
  def rank_asc(*args: AggValue) -> Aggregate:
352
+ """
353
+ Compute rank in ascending order.
354
+
355
+ Parameters
356
+ ----------
357
+ *args: AggValue
358
+ Values to rank in ascending order.
359
+
360
+ Returns
361
+ -------
362
+ Aggregate
363
+ An `Aggregate` representing the computation of the ascending rank. Returns `Integer`.
364
+
365
+ Examples
366
+ --------
367
+ Rank students by test score (lowest to highest):
368
+
369
+ >>> select(Student, aggregates.rank_asc(Student.test_score))
370
+ """
94
371
  return Aggregate(_rank, TupleVariable(args), TupleVariable([True for _ in args]))
95
372
 
373
+ @include_in_docs
96
374
  def rank_desc(*args: AggValue) -> Aggregate:
375
+ """
376
+ Compute rank in descending order.
377
+
378
+ Parameters
379
+ ----------
380
+ *args: AggValue
381
+ Values to rank in descending order.
382
+
383
+ Returns
384
+ -------
385
+ Aggregate
386
+ An `Aggregate` representing the computation of the descending rank. Returns `Integer`.
387
+
388
+ Examples
389
+ --------
390
+ Rank students by test score (highest to lowest):
391
+
392
+ >>> select(Student, aggregates.rank_desc(Student.test_score))
393
+ """
97
394
  return Aggregate(_rank, TupleVariable(args), TupleVariable([False for _ in args]))
98
395
 
396
+ @include_in_docs
99
397
  def top(limit: int, *args: AggValue) -> Aggregate:
398
+ """
399
+ Get the top N results in descending order.
400
+
401
+ Parameters
402
+ ----------
403
+ limit: int
404
+ Number of top results to return.
405
+ *args: AggValue
406
+ Values to order by (descending).
407
+
408
+ Returns
409
+ -------
410
+ Aggregate
411
+ An `Aggregate` representing the computation of the top N results.
412
+
413
+ Examples
414
+ --------
415
+ Get top 10 highest-paid employees:
416
+
417
+ >>> select(Employee).where(aggregates.top(10, Employee.salary))
418
+
419
+ Get top 3 products by revenue per store:
420
+
421
+ >>> select(Product).where(aggregates.top(3, Product.revenue).per(Store).where(Product.store == Store))
422
+ """
100
423
  return Aggregate(_limit, limit, TupleVariable(args), TupleVariable([False for _ in args]))
101
424
 
425
+ @include_in_docs
102
426
  def bottom(limit: int, *args: AggValue) -> Aggregate:
427
+ """
428
+ Get the bottom N results in ascending order.
429
+
430
+ Parameters
431
+ ----------
432
+ limit: int
433
+ Number of bottom results to return.
434
+ *args: AggValue
435
+ Values to order by (ascending).
436
+
437
+ Returns
438
+ -------
439
+ Aggregate
440
+ An `Aggregate` representing the computation of the bottom N results.
441
+
442
+ Examples
443
+ --------
444
+ Get 10 lowest-priced products:
445
+
446
+ >>> select(Product).where(aggregates.bottom(10, Product.price))
447
+
448
+ Get 5 least selling products per category:
449
+
450
+ >>> select(Product).where(aggregates.bottom(5, Product.units_sold).per(Category).where(Product.category == Category))
451
+ """
103
452
  return Aggregate(_limit, limit, TupleVariable(args), TupleVariable([True for _ in args]))
104
453
 
105
454
  #------------------------------------------------------
106
455
  # Per
107
456
  #------------------------------------------------------
108
457
 
458
+ @include_in_docs
109
459
  class Per(Group):
460
+ """
461
+ Group aggregation context for computing per-group aggregates.
110
462
 
463
+ Use this class to perform aggregations within groups defined by specific values.
464
+ """
465
+
466
+ @include_in_docs
111
467
  def sum(self, *args: AggValue) -> Aggregate:
468
+ """
469
+ Compute sum per group.
470
+
471
+ Parameters
472
+ ----------
473
+ *args: AggValue
474
+ Values to sum within each group.
475
+
476
+ Returns
477
+ -------
478
+ Aggregate
479
+ An `Aggregate` expression for per-group sum. Returns `Number` if the input is `Number`, or `Float` if the input is `Float`.
480
+
481
+ Examples
482
+ --------
483
+ Sum order amounts per customer:
484
+
485
+ >>> select(Customer, aggregates.sum(Order.amount).per(Customer).where(Order.customer == Customer))
486
+ """
112
487
  return sum(*args).per(*self._args)
113
488
 
489
+ @include_in_docs
114
490
  def count(self, *args: AggValue) -> Aggregate:
491
+ """
492
+ Count values per group.
493
+
494
+ Parameters
495
+ ----------
496
+ *args: AggValue
497
+ Values to count within each group.
498
+
499
+ Returns
500
+ -------
501
+ Aggregate
502
+ An `Aggregate` expression for per-group count. Returns `Integer`.
503
+
504
+ Examples
505
+ --------
506
+ Count employees per department:
507
+
508
+ >>> select(Department, aggregates.count(Employee).per(Department).where(Employee.department == Department))
509
+ """
115
510
  return count(*args).per(*self._args)
116
511
 
512
+ @include_in_docs
117
513
  def min(self, *args: AggValue) -> Aggregate:
514
+ """
515
+ Find minimum per group.
516
+
517
+ Parameters
518
+ ----------
519
+ *args: AggValue
520
+ Values to find minimum from within each group.
521
+
522
+ Returns
523
+ -------
524
+ Aggregate
525
+ An `Aggregate` expression for per-group minimum. Returns the same type as the input.
526
+
527
+ Examples
528
+ --------
529
+ Find minimum salary per department:
530
+
531
+ >>> select(Department, aggregates.min(Employee.salary).per(Department).where(Employee.department == Department))
532
+ """
118
533
  return min(*args).per(*self._args)
119
534
 
535
+ @include_in_docs
120
536
  def max(self, *args: AggValue) -> Aggregate:
537
+ """
538
+ Find maximum per group.
539
+
540
+ Parameters
541
+ ----------
542
+ *args: AggValue
543
+ Values to find maximum from within each group.
544
+
545
+ Returns
546
+ -------
547
+ Aggregate
548
+ An `Aggregate` expression for per-group maximum. Returns the same type as the input.
549
+
550
+ Examples
551
+ --------
552
+ Find maximum order amount per customer:
553
+
554
+ >>> select(Customer, aggregates.max(Order.amount).per(Customer).where(Order.customer == Customer))
555
+ """
121
556
  return max(*args).per(*self._args)
122
557
 
558
+ @include_in_docs
123
559
  def avg(self, *args: AggValue) -> Aggregate:
560
+ """
561
+ Compute average per group.
562
+
563
+ Parameters
564
+ ----------
565
+ *args: AggValue
566
+ Values to average within each group.
567
+
568
+ Returns
569
+ -------
570
+ Aggregate
571
+ An `Aggregate` expression for per-group average. Returns `ScaledNumber` if the input is `Number`, or `Float` if the input is `Float`.
572
+
573
+ Examples
574
+ --------
575
+ Compute average salary per department:
576
+
577
+ >>> select(Department, aggregates.avg(Employee.salary).per(Department).where(Employee.department == Department))
578
+ """
124
579
  return avg(*args).per(*self._args)
125
580
 
581
+ @include_in_docs
126
582
  def string_join(self, *args: AggValue, sep="", index=1) -> Aggregate:
583
+ """
584
+ Join strings per group.
585
+
586
+ Parameters
587
+ ----------
588
+ *args: AggValue
589
+ String values to join within each group.
590
+ sep: str
591
+ Separator string. Default: empty string.
592
+ index: int
593
+ Index parameter. Default: 1.
594
+
595
+ Returns
596
+ -------
597
+ Aggregate
598
+ An `Aggregate` expression for per-group string join. Returns `String`.
599
+
600
+ Examples
601
+ --------
602
+ Join employee names per department:
603
+
604
+ >>> select(Department, aggregates.string_join(Employee.name, sep=", ").per(Department).where(Employee.department == Department))
605
+ """
127
606
  return string_join(*args, sep=sep, index=index).per(*self._args)
128
607
 
608
+ @include_in_docs
129
609
  def rank(self, *args: AggValue|Ordering) -> Aggregate:
610
+ """
611
+ Compute rank per group.
612
+
613
+ Parameters
614
+ ----------
615
+ *args: AggValue | Ordering
616
+ Values or Ordering specs to rank by within each group.
617
+
618
+ Returns
619
+ -------
620
+ Aggregate
621
+ An `Aggregate` expression for per-group rank. Returns `Integer`.
622
+
623
+ Examples
624
+ --------
625
+ Rank employees by salary within each department:
626
+
627
+ >>> select(Employee, aggregates.rank(aggregates.desc(Employee.salary)).per(Department).where(Employee.department == Department))
628
+ """
130
629
  return rank(*args).per(*self._args)
131
630
 
631
+ @include_in_docs
132
632
  def limit(self, limit_: int, *args: AggValue|Ordering) -> Aggregate:
633
+ """
634
+ Limit results per group.
635
+
636
+ Parameters
637
+ ----------
638
+ limit_: int
639
+ Maximum results per group.
640
+ *args: AggValue | Ordering
641
+ Values or Ordering specs to order by.
642
+
643
+ Returns
644
+ -------
645
+ Aggregate
646
+ An `Aggregate` expression for per-group limited results.
647
+
648
+ Examples
649
+ --------
650
+ Get top 3 employees per department by salary:
651
+
652
+ >>> select(Employee).where(aggregates.limit(3, aggregates.desc(Employee.salary)).per(Department).where(Employee.department == Department))
653
+ """
133
654
  return limit(limit_, *args).per(*self._args)
134
655
 
656
+ @include_in_docs
135
657
  def rank_asc(self, *args: AggValue) -> Aggregate:
658
+ """
659
+ Compute ascending rank per group.
660
+
661
+ Parameters
662
+ ----------
663
+ *args: AggValue
664
+ Values to rank in ascending order within each group.
665
+
666
+ Returns
667
+ -------
668
+ Aggregate
669
+ An `Aggregate` expression for per-group ascending rank. Returns `Integer`.
670
+
671
+ Examples
672
+ --------
673
+ Rank products by price within each category (lowest first):
674
+
675
+ >>> select(Product, aggregates.rank_asc(Product.price).per(Category))
676
+ """
136
677
  return rank_asc(*args).per(*self._args)
137
678
 
679
+ @include_in_docs
138
680
  def rank_desc(self, *args: AggValue) -> Aggregate:
681
+ """
682
+ Compute descending rank per group.
683
+
684
+ Parameters
685
+ ----------
686
+ *args: AggValue
687
+ Values to rank in descending order within each group.
688
+
689
+ Returns
690
+ -------
691
+ Aggregate
692
+ An `Aggregate` expression for per-group descending rank. Returns `Integer`.
693
+
694
+ Examples
695
+ --------
696
+ Rank products by sales within each category (highest first):
697
+
698
+ >>> select(Product, aggregates.rank_desc(Product.sales).per(Category).where(Product.category == Category))
699
+ """
139
700
  return rank_desc(*args).per(*self._args)
140
701
 
702
+ @include_in_docs
141
703
  def top(self, limit: int, *args: AggValue) -> Aggregate:
704
+ """
705
+ Get top N per group.
706
+
707
+ Parameters
708
+ ----------
709
+ limit: int
710
+ Number of top results per group.
711
+ *args: AggValue
712
+ Values to order by (descending).
713
+
714
+ Returns
715
+ -------
716
+ Aggregate
717
+ An `Aggregate` expression for per-group top N.
718
+
719
+ Examples
720
+ --------
721
+ Get top 5 products by revenue per store:
722
+
723
+ >>> select(Product).where(aggregates.top(5, Product.revenue).per(Store).where(Product.store == Store))
724
+ """
142
725
  return top(limit, *args).per(*self._args)
143
726
 
727
+ @include_in_docs
144
728
  def bottom(self, limit: int, *args: AggValue) -> Aggregate:
729
+ """
730
+ Get bottom N per group.
731
+
732
+ Parameters
733
+ ----------
734
+ limit: int
735
+ Number of bottom results per group.
736
+ *args: AggValue
737
+ Values to order by (ascending).
738
+
739
+ Returns
740
+ -------
741
+ Aggregate
742
+ An `Aggregate` expression for per-group bottom N.
743
+
744
+ Examples
745
+ --------
746
+ Get bottom 3 products by sales per category:
747
+
748
+ >>> select(Product).where(aggregates.bottom(3, Product.sales).per(Category).where(Product.category == Category))
749
+ """
145
750
  return bottom(limit, *args).per(*self._args)
146
751
 
752
+ @include_in_docs
147
753
  def per(*args: Value) -> Per:
754
+ """
755
+ Create a grouped aggregation context.
756
+
757
+ Parameters
758
+ ----------
759
+ *args: Value
760
+ Values defining the grouping.
761
+
762
+ Returns
763
+ -------
764
+ Per
765
+ A `Per` object for performing grouped aggregations.
766
+
767
+ Examples
768
+ --------
769
+ Compute sum per category:
770
+
771
+ >>> aggregates.per(Category).sum(amount)
772
+
773
+ Count items per department:
774
+
775
+ >>> aggregates.per(Department).count()
776
+ """
148
777
  return Per(*args)