We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add support for FT.AGGREGATE with LOAD *
redis-py should support recent changes to RediSearch. For reference, see the following:  RediSearch/RediSearch#2301

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redis/commands/search/aggregation.py`
Content:
```
1 FIELDNAME = object()
2 
3 
4 class Limit:
5     def __init__(self, offset=0, count=0):
6         self.offset = offset
7         self.count = count
8 
9     def build_args(self):
10         if self.count:
11             return ["LIMIT", str(self.offset), str(self.count)]
12         else:
13             return []
14 
15 
16 class Reducer:
17     """
18     Base reducer object for all reducers.
19 
20     See the `redisearch.reducers` module for the actual reducers.
21     """
22 
23     NAME = None
24 
25     def __init__(self, *args):
26         self._args = args
27         self._field = None
28         self._alias = None
29 
30     def alias(self, alias):
31         """
32         Set the alias for this reducer.
33 
34         ### Parameters
35 
36         - **alias**: The value of the alias for this reducer. If this is the
37             special value `aggregation.FIELDNAME` then this reducer will be
38             aliased using the same name as the field upon which it operates.
39             Note that using `FIELDNAME` is only possible on reducers which
40             operate on a single field value.
41 
42         This method returns the `Reducer` object making it suitable for
43         chaining.
44         """
45         if alias is FIELDNAME:
46             if not self._field:
47                 raise ValueError("Cannot use FIELDNAME alias with no field")
48             # Chop off initial '@'
49             alias = self._field[1:]
50         self._alias = alias
51         return self
52 
53     @property
54     def args(self):
55         return self._args
56 
57 
58 class SortDirection:
59     """
60     This special class is used to indicate sort direction.
61     """
62 
63     DIRSTRING = None
64 
65     def __init__(self, field):
66         self.field = field
67 
68 
69 class Asc(SortDirection):
70     """
71     Indicate that the given field should be sorted in ascending order
72     """
73 
74     DIRSTRING = "ASC"
75 
76 
77 class Desc(SortDirection):
78     """
79     Indicate that the given field should be sorted in descending order
80     """
81 
82     DIRSTRING = "DESC"
83 
84 
85 class AggregateRequest:
86     """
87     Aggregation request which can be passed to `Client.aggregate`.
88     """
89 
90     def __init__(self, query="*"):
91         """
92         Create an aggregation request. This request may then be passed to
93         `client.aggregate()`.
94 
95         In order for the request to be usable, it must contain at least one
96         group.
97 
98         - **query** Query string for filtering records.
99 
100         All member methods (except `build_args()`)
101         return the object itself, making them useful for chaining.
102         """
103         self._query = query
104         self._aggregateplan = []
105         self._loadfields = []
106         self._limit = Limit()
107         self._max = 0
108         self._with_schema = False
109         self._verbatim = False
110         self._cursor = []
111 
112     def load(self, *fields):
113         """
114         Indicate the fields to be returned in the response. These fields are
115         returned in addition to any others implicitly specified.
116 
117         ### Parameters
118 
119         - **fields**: One or more fields in the format of `@field`
120         """
121         self._loadfields.extend(fields)
122         return self
123 
124     def group_by(self, fields, *reducers):
125         """
126         Specify by which fields to group the aggregation.
127 
128         ### Parameters
129 
130         - **fields**: Fields to group by. This can either be a single string,
131             or a list of strings. both cases, the field should be specified as
132             `@field`.
133         - **reducers**: One or more reducers. Reducers may be found in the
134             `aggregation` module.
135         """
136         fields = [fields] if isinstance(fields, str) else fields
137         reducers = [reducers] if isinstance(reducers, Reducer) else reducers
138 
139         ret = ["GROUPBY", str(len(fields)), *fields]
140         for reducer in reducers:
141             ret += ["REDUCE", reducer.NAME, str(len(reducer.args))]
142             ret.extend(reducer.args)
143             if reducer._alias is not None:
144                 ret += ["AS", reducer._alias]
145 
146         self._aggregateplan.extend(ret)
147         return self
148 
149     def apply(self, **kwexpr):
150         """
151         Specify one or more projection expressions to add to each result
152 
153         ### Parameters
154 
155         - **kwexpr**: One or more key-value pairs for a projection. The key is
156             the alias for the projection, and the value is the projection
157             expression itself, for example `apply(square_root="sqrt(@foo)")`
158         """
159         for alias, expr in kwexpr.items():
160             ret = ["APPLY", expr]
161             if alias is not None:
162                 ret += ["AS", alias]
163             self._aggregateplan.extend(ret)
164 
165         return self
166 
167     def limit(self, offset, num):
168         """
169         Sets the limit for the most recent group or query.
170 
171         If no group has been defined yet (via `group_by()`) then this sets
172         the limit for the initial pool of results from the query. Otherwise,
173         this limits the number of items operated on from the previous group.
174 
175         Setting a limit on the initial search results may be useful when
176         attempting to execute an aggregation on a sample of a large data set.
177 
178         ### Parameters
179 
180         - **offset**: Result offset from which to begin paging
181         - **num**: Number of results to return
182 
183 
184         Example of sorting the initial results:
185 
186         ```
187         AggregateRequest("@sale_amount:[10000, inf]")\
188             .limit(0, 10)\
189             .group_by("@state", r.count())
190         ```
191 
192         Will only group by the states found in the first 10 results of the
193         query `@sale_amount:[10000, inf]`. On the other hand,
194 
195         ```
196         AggregateRequest("@sale_amount:[10000, inf]")\
197             .limit(0, 1000)\
198             .group_by("@state", r.count()\
199             .limit(0, 10)
200         ```
201 
202         Will group all the results matching the query, but only return the
203         first 10 groups.
204 
205         If you only wish to return a *top-N* style query, consider using
206         `sort_by()` instead.
207 
208         """
209         self._limit = Limit(offset, num)
210         return self
211 
212     def sort_by(self, *fields, **kwargs):
213         """
214         Indicate how the results should be sorted. This can also be used for
215         *top-N* style queries
216 
217         ### Parameters
218 
219         - **fields**: The fields by which to sort. This can be either a single
220             field or a list of fields. If you wish to specify order, you can
221             use the `Asc` or `Desc` wrapper classes.
222         - **max**: Maximum number of results to return. This can be
223             used instead of `LIMIT` and is also faster.
224 
225 
226         Example of sorting by `foo` ascending and `bar` descending:
227 
228         ```
229         sort_by(Asc("@foo"), Desc("@bar"))
230         ```
231 
232         Return the top 10 customers:
233 
234         ```
235         AggregateRequest()\
236             .group_by("@customer", r.sum("@paid").alias(FIELDNAME))\
237             .sort_by(Desc("@paid"), max=10)
238         ```
239         """
240         if isinstance(fields, (str, SortDirection)):
241             fields = [fields]
242 
243         fields_args = []
244         for f in fields:
245             if isinstance(f, SortDirection):
246                 fields_args += [f.field, f.DIRSTRING]
247             else:
248                 fields_args += [f]
249 
250         ret = ["SORTBY", str(len(fields_args))]
251         ret.extend(fields_args)
252         max = kwargs.get("max", 0)
253         if max > 0:
254             ret += ["MAX", str(max)]
255 
256         self._aggregateplan.extend(ret)
257         return self
258 
259     def filter(self, expressions):
260         """
261         Specify filter for post-query results using predicates relating to
262         values in the result set.
263 
264         ### Parameters
265 
266         - **fields**: Fields to group by. This can either be a single string,
267             or a list of strings.
268         """
269         if isinstance(expressions, str):
270             expressions = [expressions]
271 
272         for expression in expressions:
273             self._aggregateplan.extend(["FILTER", expression])
274 
275         return self
276 
277     def with_schema(self):
278         """
279         If set, the `schema` property will contain a list of `[field, type]`
280         entries in the result object.
281         """
282         self._with_schema = True
283         return self
284 
285     def verbatim(self):
286         self._verbatim = True
287         return self
288 
289     def cursor(self, count=0, max_idle=0.0):
290         args = ["WITHCURSOR"]
291         if count:
292             args += ["COUNT", str(count)]
293         if max_idle:
294             args += ["MAXIDLE", str(max_idle * 1000)]
295         self._cursor = args
296         return self
297 
298     def build_args(self):
299         # @foo:bar ...
300         ret = [self._query]
301 
302         if self._with_schema:
303             ret.append("WITHSCHEMA")
304 
305         if self._verbatim:
306             ret.append("VERBATIM")
307 
308         if self._cursor:
309             ret += self._cursor
310 
311         if self._loadfields:
312             ret.append("LOAD")
313             ret.append(str(len(self._loadfields)))
314             ret.extend(self._loadfields)
315 
316         ret.extend(self._aggregateplan)
317 
318         ret += self._limit.build_args()
319 
320         return ret
321 
322 
323 class Cursor:
324     def __init__(self, cid):
325         self.cid = cid
326         self.max_idle = 0
327         self.count = 0
328 
329     def build_args(self):
330         args = [str(self.cid)]
331         if self.max_idle:
332             args += ["MAXIDLE", str(self.max_idle)]
333         if self.count:
334             args += ["COUNT", str(self.count)]
335         return args
336 
337 
338 class AggregateResult:
339     def __init__(self, rows, cursor, schema):
340         self.rows = rows
341         self.cursor = cursor
342         self.schema = schema
343 
344     def __repr__(self):
345         cid = self.cursor.cid if self.cursor else -1
346         return (
347             f"<{self.__class__.__name__} at 0x{id(self):x} "
348             f"Rows={len(self.rows)}, Cursor={cid}>"
349         )
350
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/redis/commands/search/aggregation.py b/redis/commands/search/aggregation.py
--- a/redis/commands/search/aggregation.py
+++ b/redis/commands/search/aggregation.py
@@ -103,6 +103,7 @@
         self._query = query
         self._aggregateplan = []
         self._loadfields = []
+        self._loadall = False
         self._limit = Limit()
         self._max = 0
         self._with_schema = False
@@ -116,9 +117,13 @@
 
         ### Parameters
 
-        - **fields**: One or more fields in the format of `@field`
+        - **fields**: If fields not specified, all the fields will be loaded.
+        Otherwise, fields should be given in the format of `@field`.
         """
-        self._loadfields.extend(fields)
+        if fields:
+            self._loadfields.extend(fields)
+        else:
+            self._loadall = True
         return self
 
     def group_by(self, fields, *reducers):
@@ -308,7 +313,10 @@
         if self._cursor:
             ret += self._cursor
 
-        if self._loadfields:
+        if self._loadall:
+            ret.append("LOAD")
+            ret.append("*")
+        elif self._loadfields:
             ret.append("LOAD")
             ret.append(str(len(self._loadfields)))
             ret.extend(self._loadfields)

{"golden_diff": "diff --git a/redis/commands/search/aggregation.py b/redis/commands/search/aggregation.py\n--- a/redis/commands/search/aggregation.py\n+++ b/redis/commands/search/aggregation.py\n@@ -103,6 +103,7 @@\n         self._query = query\n         self._aggregateplan = []\n         self._loadfields = []\n+        self._loadall = False\n         self._limit = Limit()\n         self._max = 0\n         self._with_schema = False\n@@ -116,9 +117,13 @@\n \n         ### Parameters\n \n-        - **fields**: One or more fields in the format of `@field`\n+        - **fields**: If fields not specified, all the fields will be loaded.\n+        Otherwise, fields should be given in the format of `@field`.\n         \"\"\"\n-        self._loadfields.extend(fields)\n+        if fields:\n+            self._loadfields.extend(fields)\n+        else:\n+            self._loadall = True\n         return self\n \n     def group_by(self, fields, *reducers):\n@@ -308,7 +313,10 @@\n         if self._cursor:\n             ret += self._cursor\n \n-        if self._loadfields:\n+        if self._loadall:\n+            ret.append(\"LOAD\")\n+            ret.append(\"*\")\n+        elif self._loadfields:\n             ret.append(\"LOAD\")\n             ret.append(str(len(self._loadfields)))\n             ret.extend(self._loadfields)\n", "issue": "Add support for FT.AGGREGATE with LOAD *\nredis-py should support recent changes to RediSearch. For reference, see the following:  RediSearch/RediSearch#2301\n", "before_files": [{"content": "FIELDNAME = object()\n\n\nclass Limit:\n    def __init__(self, offset=0, count=0):\n        self.offset = offset\n        self.count = count\n\n    def build_args(self):\n        if self.count:\n            return [\"LIMIT\", str(self.offset), str(self.count)]\n        else:\n            return []\n\n\nclass Reducer:\n    \"\"\"\n    Base reducer object for all reducers.\n\n    See the `redisearch.reducers` module for the actual reducers.\n    \"\"\"\n\n    NAME = None\n\n    def __init__(self, *args):\n        self._args = args\n        self._field = None\n        self._alias = None\n\n    def alias(self, alias):\n        \"\"\"\n        Set the alias for this reducer.\n\n        ### Parameters\n\n        - **alias**: The value of the alias for this reducer. If this is the\n            special value `aggregation.FIELDNAME` then this reducer will be\n            aliased using the same name as the field upon which it operates.\n            Note that using `FIELDNAME` is only possible on reducers which\n            operate on a single field value.\n\n        This method returns the `Reducer` object making it suitable for\n        chaining.\n        \"\"\"\n        if alias is FIELDNAME:\n            if not self._field:\n                raise ValueError(\"Cannot use FIELDNAME alias with no field\")\n            # Chop off initial '@'\n            alias = self._field[1:]\n        self._alias = alias\n        return self\n\n    @property\n    def args(self):\n        return self._args\n\n\nclass SortDirection:\n    \"\"\"\n    This special class is used to indicate sort direction.\n    \"\"\"\n\n    DIRSTRING = None\n\n    def __init__(self, field):\n        self.field = field\n\n\nclass Asc(SortDirection):\n    \"\"\"\n    Indicate that the given field should be sorted in ascending order\n    \"\"\"\n\n    DIRSTRING = \"ASC\"\n\n\nclass Desc(SortDirection):\n    \"\"\"\n    Indicate that the given field should be sorted in descending order\n    \"\"\"\n\n    DIRSTRING = \"DESC\"\n\n\nclass AggregateRequest:\n    \"\"\"\n    Aggregation request which can be passed to `Client.aggregate`.\n    \"\"\"\n\n    def __init__(self, query=\"*\"):\n        \"\"\"\n        Create an aggregation request. This request may then be passed to\n        `client.aggregate()`.\n\n        In order for the request to be usable, it must contain at least one\n        group.\n\n        - **query** Query string for filtering records.\n\n        All member methods (except `build_args()`)\n        return the object itself, making them useful for chaining.\n        \"\"\"\n        self._query = query\n        self._aggregateplan = []\n        self._loadfields = []\n        self._limit = Limit()\n        self._max = 0\n        self._with_schema = False\n        self._verbatim = False\n        self._cursor = []\n\n    def load(self, *fields):\n        \"\"\"\n        Indicate the fields to be returned in the response. These fields are\n        returned in addition to any others implicitly specified.\n\n        ### Parameters\n\n        - **fields**: One or more fields in the format of `@field`\n        \"\"\"\n        self._loadfields.extend(fields)\n        return self\n\n    def group_by(self, fields, *reducers):\n        \"\"\"\n        Specify by which fields to group the aggregation.\n\n        ### Parameters\n\n        - **fields**: Fields to group by. This can either be a single string,\n            or a list of strings. both cases, the field should be specified as\n            `@field`.\n        - **reducers**: One or more reducers. Reducers may be found in the\n            `aggregation` module.\n        \"\"\"\n        fields = [fields] if isinstance(fields, str) else fields\n        reducers = [reducers] if isinstance(reducers, Reducer) else reducers\n\n        ret = [\"GROUPBY\", str(len(fields)), *fields]\n        for reducer in reducers:\n            ret += [\"REDUCE\", reducer.NAME, str(len(reducer.args))]\n            ret.extend(reducer.args)\n            if reducer._alias is not None:\n                ret += [\"AS\", reducer._alias]\n\n        self._aggregateplan.extend(ret)\n        return self\n\n    def apply(self, **kwexpr):\n        \"\"\"\n        Specify one or more projection expressions to add to each result\n\n        ### Parameters\n\n        - **kwexpr**: One or more key-value pairs for a projection. The key is\n            the alias for the projection, and the value is the projection\n            expression itself, for example `apply(square_root=\"sqrt(@foo)\")`\n        \"\"\"\n        for alias, expr in kwexpr.items():\n            ret = [\"APPLY\", expr]\n            if alias is not None:\n                ret += [\"AS\", alias]\n            self._aggregateplan.extend(ret)\n\n        return self\n\n    def limit(self, offset, num):\n        \"\"\"\n        Sets the limit for the most recent group or query.\n\n        If no group has been defined yet (via `group_by()`) then this sets\n        the limit for the initial pool of results from the query. Otherwise,\n        this limits the number of items operated on from the previous group.\n\n        Setting a limit on the initial search results may be useful when\n        attempting to execute an aggregation on a sample of a large data set.\n\n        ### Parameters\n\n        - **offset**: Result offset from which to begin paging\n        - **num**: Number of results to return\n\n\n        Example of sorting the initial results:\n\n        ```\n        AggregateRequest(\"@sale_amount:[10000, inf]\")\\\n            .limit(0, 10)\\\n            .group_by(\"@state\", r.count())\n        ```\n\n        Will only group by the states found in the first 10 results of the\n        query `@sale_amount:[10000, inf]`. On the other hand,\n\n        ```\n        AggregateRequest(\"@sale_amount:[10000, inf]\")\\\n            .limit(0, 1000)\\\n            .group_by(\"@state\", r.count()\\\n            .limit(0, 10)\n        ```\n\n        Will group all the results matching the query, but only return the\n        first 10 groups.\n\n        If you only wish to return a *top-N* style query, consider using\n        `sort_by()` instead.\n\n        \"\"\"\n        self._limit = Limit(offset, num)\n        return self\n\n    def sort_by(self, *fields, **kwargs):\n        \"\"\"\n        Indicate how the results should be sorted. This can also be used for\n        *top-N* style queries\n\n        ### Parameters\n\n        - **fields**: The fields by which to sort. This can be either a single\n            field or a list of fields. If you wish to specify order, you can\n            use the `Asc` or `Desc` wrapper classes.\n        - **max**: Maximum number of results to return. This can be\n            used instead of `LIMIT` and is also faster.\n\n\n        Example of sorting by `foo` ascending and `bar` descending:\n\n        ```\n        sort_by(Asc(\"@foo\"), Desc(\"@bar\"))\n        ```\n\n        Return the top 10 customers:\n\n        ```\n        AggregateRequest()\\\n            .group_by(\"@customer\", r.sum(\"@paid\").alias(FIELDNAME))\\\n            .sort_by(Desc(\"@paid\"), max=10)\n        ```\n        \"\"\"\n        if isinstance(fields, (str, SortDirection)):\n            fields = [fields]\n\n        fields_args = []\n        for f in fields:\n            if isinstance(f, SortDirection):\n                fields_args += [f.field, f.DIRSTRING]\n            else:\n                fields_args += [f]\n\n        ret = [\"SORTBY\", str(len(fields_args))]\n        ret.extend(fields_args)\n        max = kwargs.get(\"max\", 0)\n        if max > 0:\n            ret += [\"MAX\", str(max)]\n\n        self._aggregateplan.extend(ret)\n        return self\n\n    def filter(self, expressions):\n        \"\"\"\n        Specify filter for post-query results using predicates relating to\n        values in the result set.\n\n        ### Parameters\n\n        - **fields**: Fields to group by. This can either be a single string,\n            or a list of strings.\n        \"\"\"\n        if isinstance(expressions, str):\n            expressions = [expressions]\n\n        for expression in expressions:\n            self._aggregateplan.extend([\"FILTER\", expression])\n\n        return self\n\n    def with_schema(self):\n        \"\"\"\n        If set, the `schema` property will contain a list of `[field, type]`\n        entries in the result object.\n        \"\"\"\n        self._with_schema = True\n        return self\n\n    def verbatim(self):\n        self._verbatim = True\n        return self\n\n    def cursor(self, count=0, max_idle=0.0):\n        args = [\"WITHCURSOR\"]\n        if count:\n            args += [\"COUNT\", str(count)]\n        if max_idle:\n            args += [\"MAXIDLE\", str(max_idle * 1000)]\n        self._cursor = args\n        return self\n\n    def build_args(self):\n        # @foo:bar ...\n        ret = [self._query]\n\n        if self._with_schema:\n            ret.append(\"WITHSCHEMA\")\n\n        if self._verbatim:\n            ret.append(\"VERBATIM\")\n\n        if self._cursor:\n            ret += self._cursor\n\n        if self._loadfields:\n            ret.append(\"LOAD\")\n            ret.append(str(len(self._loadfields)))\n            ret.extend(self._loadfields)\n\n        ret.extend(self._aggregateplan)\n\n        ret += self._limit.build_args()\n\n        return ret\n\n\nclass Cursor:\n    def __init__(self, cid):\n        self.cid = cid\n        self.max_idle = 0\n        self.count = 0\n\n    def build_args(self):\n        args = [str(self.cid)]\n        if self.max_idle:\n            args += [\"MAXIDLE\", str(self.max_idle)]\n        if self.count:\n            args += [\"COUNT\", str(self.count)]\n        return args\n\n\nclass AggregateResult:\n    def __init__(self, rows, cursor, schema):\n        self.rows = rows\n        self.cursor = cursor\n        self.schema = schema\n\n    def __repr__(self):\n        cid = self.cursor.cid if self.cursor else -1\n        return (\n            f\"<{self.__class__.__name__} at 0x{id(self):x} \"\n            f\"Rows={len(self.rows)}, Cursor={cid}>\"\n        )\n", "path": "redis/commands/search/aggregation.py"}], "after_files": [{"content": "FIELDNAME = object()\n\n\nclass Limit:\n    def __init__(self, offset=0, count=0):\n        self.offset = offset\n        self.count = count\n\n    def build_args(self):\n        if self.count:\n            return [\"LIMIT\", str(self.offset), str(self.count)]\n        else:\n            return []\n\n\nclass Reducer:\n    \"\"\"\n    Base reducer object for all reducers.\n\n    See the `redisearch.reducers` module for the actual reducers.\n    \"\"\"\n\n    NAME = None\n\n    def __init__(self, *args):\n        self._args = args\n        self._field = None\n        self._alias = None\n\n    def alias(self, alias):\n        \"\"\"\n        Set the alias for this reducer.\n\n        ### Parameters\n\n        - **alias**: The value of the alias for this reducer. If this is the\n            special value `aggregation.FIELDNAME` then this reducer will be\n            aliased using the same name as the field upon which it operates.\n            Note that using `FIELDNAME` is only possible on reducers which\n            operate on a single field value.\n\n        This method returns the `Reducer` object making it suitable for\n        chaining.\n        \"\"\"\n        if alias is FIELDNAME:\n            if not self._field:\n                raise ValueError(\"Cannot use FIELDNAME alias with no field\")\n            # Chop off initial '@'\n            alias = self._field[1:]\n        self._alias = alias\n        return self\n\n    @property\n    def args(self):\n        return self._args\n\n\nclass SortDirection:\n    \"\"\"\n    This special class is used to indicate sort direction.\n    \"\"\"\n\n    DIRSTRING = None\n\n    def __init__(self, field):\n        self.field = field\n\n\nclass Asc(SortDirection):\n    \"\"\"\n    Indicate that the given field should be sorted in ascending order\n    \"\"\"\n\n    DIRSTRING = \"ASC\"\n\n\nclass Desc(SortDirection):\n    \"\"\"\n    Indicate that the given field should be sorted in descending order\n    \"\"\"\n\n    DIRSTRING = \"DESC\"\n\n\nclass AggregateRequest:\n    \"\"\"\n    Aggregation request which can be passed to `Client.aggregate`.\n    \"\"\"\n\n    def __init__(self, query=\"*\"):\n        \"\"\"\n        Create an aggregation request. This request may then be passed to\n        `client.aggregate()`.\n\n        In order for the request to be usable, it must contain at least one\n        group.\n\n        - **query** Query string for filtering records.\n\n        All member methods (except `build_args()`)\n        return the object itself, making them useful for chaining.\n        \"\"\"\n        self._query = query\n        self._aggregateplan = []\n        self._loadfields = []\n        self._loadall = False\n        self._limit = Limit()\n        self._max = 0\n        self._with_schema = False\n        self._verbatim = False\n        self._cursor = []\n\n    def load(self, *fields):\n        \"\"\"\n        Indicate the fields to be returned in the response. These fields are\n        returned in addition to any others implicitly specified.\n\n        ### Parameters\n\n        - **fields**: If fields not specified, all the fields will be loaded.\n        Otherwise, fields should be given in the format of `@field`.\n        \"\"\"\n        if fields:\n            self._loadfields.extend(fields)\n        else:\n            self._loadall = True\n        return self\n\n    def group_by(self, fields, *reducers):\n        \"\"\"\n        Specify by which fields to group the aggregation.\n\n        ### Parameters\n\n        - **fields**: Fields to group by. This can either be a single string,\n            or a list of strings. both cases, the field should be specified as\n            `@field`.\n        - **reducers**: One or more reducers. Reducers may be found in the\n            `aggregation` module.\n        \"\"\"\n        fields = [fields] if isinstance(fields, str) else fields\n        reducers = [reducers] if isinstance(reducers, Reducer) else reducers\n\n        ret = [\"GROUPBY\", str(len(fields)), *fields]\n        for reducer in reducers:\n            ret += [\"REDUCE\", reducer.NAME, str(len(reducer.args))]\n            ret.extend(reducer.args)\n            if reducer._alias is not None:\n                ret += [\"AS\", reducer._alias]\n\n        self._aggregateplan.extend(ret)\n        return self\n\n    def apply(self, **kwexpr):\n        \"\"\"\n        Specify one or more projection expressions to add to each result\n\n        ### Parameters\n\n        - **kwexpr**: One or more key-value pairs for a projection. The key is\n            the alias for the projection, and the value is the projection\n            expression itself, for example `apply(square_root=\"sqrt(@foo)\")`\n        \"\"\"\n        for alias, expr in kwexpr.items():\n            ret = [\"APPLY\", expr]\n            if alias is not None:\n                ret += [\"AS\", alias]\n            self._aggregateplan.extend(ret)\n\n        return self\n\n    def limit(self, offset, num):\n        \"\"\"\n        Sets the limit for the most recent group or query.\n\n        If no group has been defined yet (via `group_by()`) then this sets\n        the limit for the initial pool of results from the query. Otherwise,\n        this limits the number of items operated on from the previous group.\n\n        Setting a limit on the initial search results may be useful when\n        attempting to execute an aggregation on a sample of a large data set.\n\n        ### Parameters\n\n        - **offset**: Result offset from which to begin paging\n        - **num**: Number of results to return\n\n\n        Example of sorting the initial results:\n\n        ```\n        AggregateRequest(\"@sale_amount:[10000, inf]\")\\\n            .limit(0, 10)\\\n            .group_by(\"@state\", r.count())\n        ```\n\n        Will only group by the states found in the first 10 results of the\n        query `@sale_amount:[10000, inf]`. On the other hand,\n\n        ```\n        AggregateRequest(\"@sale_amount:[10000, inf]\")\\\n            .limit(0, 1000)\\\n            .group_by(\"@state\", r.count()\\\n            .limit(0, 10)\n        ```\n\n        Will group all the results matching the query, but only return the\n        first 10 groups.\n\n        If you only wish to return a *top-N* style query, consider using\n        `sort_by()` instead.\n\n        \"\"\"\n        self._limit = Limit(offset, num)\n        return self\n\n    def sort_by(self, *fields, **kwargs):\n        \"\"\"\n        Indicate how the results should be sorted. This can also be used for\n        *top-N* style queries\n\n        ### Parameters\n\n        - **fields**: The fields by which to sort. This can be either a single\n            field or a list of fields. If you wish to specify order, you can\n            use the `Asc` or `Desc` wrapper classes.\n        - **max**: Maximum number of results to return. This can be\n            used instead of `LIMIT` and is also faster.\n\n\n        Example of sorting by `foo` ascending and `bar` descending:\n\n        ```\n        sort_by(Asc(\"@foo\"), Desc(\"@bar\"))\n        ```\n\n        Return the top 10 customers:\n\n        ```\n        AggregateRequest()\\\n            .group_by(\"@customer\", r.sum(\"@paid\").alias(FIELDNAME))\\\n            .sort_by(Desc(\"@paid\"), max=10)\n        ```\n        \"\"\"\n        if isinstance(fields, (str, SortDirection)):\n            fields = [fields]\n\n        fields_args = []\n        for f in fields:\n            if isinstance(f, SortDirection):\n                fields_args += [f.field, f.DIRSTRING]\n            else:\n                fields_args += [f]\n\n        ret = [\"SORTBY\", str(len(fields_args))]\n        ret.extend(fields_args)\n        max = kwargs.get(\"max\", 0)\n        if max > 0:\n            ret += [\"MAX\", str(max)]\n\n        self._aggregateplan.extend(ret)\n        return self\n\n    def filter(self, expressions):\n        \"\"\"\n        Specify filter for post-query results using predicates relating to\n        values in the result set.\n\n        ### Parameters\n\n        - **fields**: Fields to group by. This can either be a single string,\n            or a list of strings.\n        \"\"\"\n        if isinstance(expressions, str):\n            expressions = [expressions]\n\n        for expression in expressions:\n            self._aggregateplan.extend([\"FILTER\", expression])\n\n        return self\n\n    def with_schema(self):\n        \"\"\"\n        If set, the `schema` property will contain a list of `[field, type]`\n        entries in the result object.\n        \"\"\"\n        self._with_schema = True\n        return self\n\n    def verbatim(self):\n        self._verbatim = True\n        return self\n\n    def cursor(self, count=0, max_idle=0.0):\n        args = [\"WITHCURSOR\"]\n        if count:\n            args += [\"COUNT\", str(count)]\n        if max_idle:\n            args += [\"MAXIDLE\", str(max_idle * 1000)]\n        self._cursor = args\n        return self\n\n    def build_args(self):\n        # @foo:bar ...\n        ret = [self._query]\n\n        if self._with_schema:\n            ret.append(\"WITHSCHEMA\")\n\n        if self._verbatim:\n            ret.append(\"VERBATIM\")\n\n        if self._cursor:\n            ret += self._cursor\n\n        if self._loadall:\n            ret.append(\"LOAD\")\n            ret.append(\"*\")\n        elif self._loadfields:\n            ret.append(\"LOAD\")\n            ret.append(str(len(self._loadfields)))\n            ret.extend(self._loadfields)\n\n        ret.extend(self._aggregateplan)\n\n        ret += self._limit.build_args()\n\n        return ret\n\n\nclass Cursor:\n    def __init__(self, cid):\n        self.cid = cid\n        self.max_idle = 0\n        self.count = 0\n\n    def build_args(self):\n        args = [str(self.cid)]\n        if self.max_idle:\n            args += [\"MAXIDLE\", str(self.max_idle)]\n        if self.count:\n            args += [\"COUNT\", str(self.count)]\n        return args\n\n\nclass AggregateResult:\n    def __init__(self, rows, cursor, schema):\n        self.rows = rows\n        self.cursor = cursor\n        self.schema = schema\n\n    def __repr__(self):\n        cid = self.cursor.cid if self.cursor else -1\n        return (\n            f\"<{self.__class__.__name__} at 0x{id(self):x} \"\n            f\"Rows={len(self.rows)}, Cursor={cid}>\"\n        )\n", "path": "redis/commands/search/aggregation.py"}]}