Skip to content

Commit 2e343aa

Browse files
authored
New use_query and use_mutation API (#241)
- Fix #207
1 parent 2949aa6 commit 2e343aa

17 files changed

+161
-227
lines changed

Diff for: CHANGELOG.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,21 @@ Using the following categories, list your changes in this order:
3434

3535
## [Unreleased]
3636

37-
- Nothing (yet)!
37+
### Changed
38+
39+
- New syntax for `use_query` and `use_mutation` hooks. Here's a quick comparison of the changes:
40+
41+
```python
42+
query = use_query(QueryOptions(thread_sensitive=True), get_items, value=123456, foo="bar") # Old
43+
query = use_query(get_items, {"value":12356, "foo":"bar"}, thread_sensitive=True) # New
44+
45+
mutation = use_mutation(MutationOptions(thread_sensitive=True), remove_item) # Old
46+
mutation = use_mutation(remove_item, thread_sensitive=True) # New
47+
```
48+
49+
### Removed
50+
51+
- `QueryOptions` and `MutationOptions` have been removed. Their values are now passed direct into the hook.
3852

3953
## [3.8.1] - 2024-05-07
4054

Diff for: docs/examples/python/django-query-postprocessor.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from example.models import TodoItem
22
from reactpy import component
33
from reactpy_django.hooks import use_query
4-
from reactpy_django.types import QueryOptions
54
from reactpy_django.utils import django_query_postprocessor
65

76

@@ -11,13 +10,11 @@ def get_items():
1110

1211
@component
1312
def todo_list():
14-
# These `QueryOptions` are functionally equivalent to ReactPy-Django's default values
13+
# These postprocessor options are functionally equivalent to ReactPy-Django's default values
1514
item_query = use_query(
16-
QueryOptions(
17-
postprocessor=django_query_postprocessor,
18-
postprocessor_kwargs={"many_to_many": True, "many_to_one": True},
19-
),
2015
get_items,
16+
postprocessor=django_query_postprocessor,
17+
postprocessor_kwargs={"many_to_many": True, "many_to_one": True},
2118
)
2219

2320
return item_query.data

Diff for: docs/examples/python/use-mutation-query-refetch.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ def submit_event(event):
2626
elif item_query.error or not item_query.data:
2727
rendered_items = html.h2("Error when loading!")
2828
else:
29-
rendered_items = html.ul(html.li(item, key=item.pk) for item in item_query.data)
29+
rendered_items = html.ul(
30+
html.li(item.text, key=item.pk) for item in item_query.data
31+
)
3032

3133
# Handle all possible mutation states
3234
if item_mutation.loading:

Diff for: docs/examples/python/use-mutation-thread-sensitive.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from reactpy import component, html
22
from reactpy_django.hooks import use_mutation
3-
from reactpy_django.types import MutationOptions
43

54

65
def execute_thread_safe_mutation(text):
@@ -11,8 +10,8 @@ def execute_thread_safe_mutation(text):
1110
@component
1211
def my_component():
1312
item_mutation = use_mutation(
14-
MutationOptions(thread_sensitive=False),
1513
execute_thread_safe_mutation,
14+
thread_sensitive=False,
1615
)
1716

1817
def submit_event(event):

Diff for: docs/examples/python/use-query-args.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@
22
from reactpy_django.hooks import use_query
33

44

5-
def example_query(value: int, other_value: bool = False):
6-
...
5+
def example_query(value: int, other_value: bool = False): ...
76

87

98
@component
109
def my_component():
11-
query = use_query(
12-
example_query,
13-
123,
14-
other_value=True,
15-
)
10+
query = use_query(example_query, {"value": 123, "other_value": True})
1611

1712
return str(query.data)

Diff for: docs/examples/python/use-query-postprocessor-change.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from reactpy import component
22
from reactpy_django.hooks import use_query
3-
from reactpy_django.types import QueryOptions
43

54

65
def my_postprocessor(data, example_kwarg=True):
@@ -18,11 +17,9 @@ def execute_io_intensive_operation():
1817
@component
1918
def my_component():
2019
query = use_query(
21-
QueryOptions(
22-
postprocessor=my_postprocessor,
23-
postprocessor_kwargs={"example_kwarg": False},
24-
),
2520
execute_io_intensive_operation,
21+
postprocessor=my_postprocessor,
22+
postprocessor_kwargs={"example_kwarg": False},
2623
)
2724

2825
if query.loading or query.error:

Diff for: docs/examples/python/use-query-postprocessor-disable.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from reactpy import component
22
from reactpy_django.hooks import use_query
3-
from reactpy_django.types import QueryOptions
43

54

65
def execute_io_intensive_operation():
@@ -11,8 +10,8 @@ def execute_io_intensive_operation():
1110
@component
1211
def my_component():
1312
query = use_query(
14-
QueryOptions(postprocessor=None),
1513
execute_io_intensive_operation,
14+
postprocessor=None,
1615
)
1716

1817
if query.loading or query.error:

Diff for: docs/examples/python/use-query-postprocessor-kwargs.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from example.models import TodoItem
22
from reactpy import component
33
from reactpy_django.hooks import use_query
4-
from reactpy_django.types import QueryOptions
54

65

76
def get_model_with_relationships():
@@ -17,10 +16,8 @@ def get_model_with_relationships():
1716
@component
1817
def my_component():
1918
query = use_query(
20-
QueryOptions(
21-
postprocessor_kwargs={"many_to_many": False, "many_to_one": False}
22-
),
2319
get_model_with_relationships,
20+
postprocessor_kwargs={"many_to_many": False, "many_to_one": False},
2421
)
2522

2623
if query.loading or query.error or not query.data:

Diff for: docs/examples/python/use-query-thread-sensitive.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from reactpy import component
22
from reactpy_django.hooks import use_query
3-
from reactpy_django.types import QueryOptions
43

54

65
def execute_thread_safe_operation():
@@ -10,10 +9,7 @@ def execute_thread_safe_operation():
109

1110
@component
1211
def my_component():
13-
query = use_query(
14-
QueryOptions(thread_sensitive=False),
15-
execute_thread_safe_operation,
16-
)
12+
query = use_query(execute_thread_safe_operation, thread_sensitive=False)
1713

1814
if query.loading or query.error:
1915
return None

Diff for: docs/examples/python/use-query.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def todo_list():
1818
rendered_items = html.h2("Error when loading!")
1919
else:
2020
rendered_items = html.ul(
21-
[html.li(item, key=item.pk) for item in item_query.data]
21+
[html.li(item.text, key=item.pk) for item in item_query.data]
2222
)
2323

2424
return html.div("Rendered items: ", rendered_items)

Diff for: docs/includes/orm.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ These `#!python SynchronousOnlyOperation` exceptions may be removed in a future
88

99
<!--orm-fetch-start-->
1010

11-
By default, automatic recursive fetching of `#!python ManyToMany` or `#!python ForeignKey` fields is enabled within the `django_query_postprocessor`. This is needed to prevent `#!python SynchronousOnlyOperation` exceptions when accessing these fields within your ReactPy components.
11+
By default, automatic recursive fetching of `#!python ManyToMany` or `#!python ForeignKey` fields is enabled within the `#!python django_query_postprocessor`. This is needed to prevent `#!python SynchronousOnlyOperation` exceptions when accessing these fields within your ReactPy components.
1212

1313
<!--orm-fetch-end-->

Diff for: docs/src/reference/hooks.md

+23-25
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,21 @@ Query functions can be sync or async.
4444

4545
| Name | Type | Description | Default |
4646
| --- | --- | --- | --- |
47-
| `#!python options` | `#!python QueryOptions | None` | An optional `#!python QueryOptions` object that can modify how the query is executed. | `#!python None` |
48-
| `#!python query` | `#!python Callable[_Params, _Result | None]` | A callable that returns a Django `#!python Model` or `#!python QuerySet`. | N/A |
49-
| `#!python *args` | `#!python _Params.args` | Positional arguments to pass into `#!python query`. | N/A |
50-
| `#!python **kwargs` | `#!python _Params.kwargs` | Keyword arguments to pass into `#!python query`. | N/A |
47+
| `#!python query` | `#!python Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred]` | A function that executes a query and returns some data. | N/A |
48+
| `#!python kwargs` | `#!python dict[str, Any] | None` | Keyword arguments to passed into the `#!python query` function. | `#!python None` |
49+
| `#!python thread_sensitive` | `#!python bool` | Whether to run your query function in thread sensitive mode. This mode only applies to sync query functions, and is turned on by default due to Django ORM limitations. | `#!python True` |
50+
| `#!python postprocessor` | `#!python AsyncPostprocessor | SyncPostprocessor | None` | A callable that processes the query `#!python data` before it is returned. The first argument of postprocessor function must be the query `#!python data`. All proceeding arguments are optional `#!python postprocessor_kwargs`. This postprocessor function must return the modified `#!python data`. | `#!python None` |
51+
| `#!python postprocessor_kwargs` | `#!python dict[str, Any] | None` | Keyworded arguments passed into the `#!python postprocessor` function. | `#!python None` |
5152

5253
<font size="4">**Returns**</font>
5354

5455
| Type | Description |
5556
| --- | --- |
56-
| `#!python Query[_Result | None]` | An object containing `#!python loading`/`#!python error` states, your `#!python data` (if the query has successfully executed), and a `#!python refetch` callable that can be used to re-run the query. |
57+
| `#!python Query[Inferred]` | An object containing `#!python loading`/`#!python error` states, your `#!python data` (if the query has successfully executed), and a `#!python refetch` callable that can be used to re-run the query. |
5758

5859
??? question "How can I provide arguments to my query function?"
5960

60-
`#!python *args` and `#!python **kwargs` can be provided to your query function via `#!python use_query` parameters.
61+
`#!python kwargs` can be provided to your query function via the `#!python kwargs=...` parameter.
6162

6263
=== "components.py"
6364

@@ -67,15 +68,15 @@ Query functions can be sync or async.
6768

6869
??? question "How can I customize this hook's behavior?"
6970

70-
This hook accepts a `#!python options: QueryOptions` parameter that can be used to customize behavior.
71+
This hook has several parameters that can be used to customize behavior.
7172

72-
Below are the settings that can be modified via these `#!python QueryOptions`.
73+
Below are examples of values that can be modified.
7374

7475
---
7576

7677
<font size="4">**`#!python thread_sensitive`**</font>
7778

78-
Whether to run your synchronous query function in thread-sensitive mode. Thread-sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
79+
Whether to run your synchronous query function in thread sensitive mode. Thread sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
7980

8081
This setting only applies to sync query functions, and will be ignored for async functions.
8182

@@ -96,19 +97,15 @@ Query functions can be sync or async.
9697
1. Want to use this hook to defer IO intensive tasks to be computed in the background
9798
2. Want to to utilize `#!python use_query` with a different ORM
9899

99-
... then you can either set a custom `#!python postprocessor`, or disable all postprocessing behavior by modifying the `#!python QueryOptions.postprocessor` parameter. In the example below, we will set the `#!python postprocessor` to `#!python None` to disable postprocessing behavior.
100+
... then you can either set a custom `#!python postprocessor`, or disable all postprocessing behavior by modifying the `#!python postprocessor=...` parameter. In the example below, we will set the `#!python postprocessor` to `#!python None` to disable postprocessing behavior.
100101

101102
=== "components.py"
102103

103104
```python
104105
{% include "../../examples/python/use-query-postprocessor-disable.py" %}
105106
```
106107

107-
If you wish to create a custom `#!python postprocessor`, you will need to create a callable.
108-
109-
The first argument of `#!python postprocessor` must be the query `#!python data`. All proceeding arguments
110-
are optional `#!python postprocessor_kwargs` (see below). This `#!python postprocessor` must return
111-
the modified `#!python data`.
108+
If you wish to create a custom `#!python postprocessor`, you will need to create a function where the first must be the query `#!python data`. All proceeding arguments are optional `#!python postprocessor_kwargs` (see below). This `#!python postprocessor` function must return the modified `#!python data`.
112109

113110
=== "components.py"
114111

@@ -124,7 +121,7 @@ Query functions can be sync or async.
124121

125122
However, if you have deep nested trees of relational data, this may not be a desirable behavior. In these scenarios, you may prefer to manually fetch these relational fields using a second `#!python use_query` hook.
126123

127-
You can disable the prefetching behavior of the default `#!python postprocessor` (located at `#!python reactpy_django.utils.django_query_postprocessor`) via the `#!python QueryOptions.postprocessor_kwargs` parameter.
124+
You can disable the prefetching behavior of the default `#!python postprocessor` (located at `#!python reactpy_django.utils.django_query_postprocessor`) via the `#!python postprocessor_kwargs=...` parameter.
128125

129126
=== "components.py"
130127

@@ -140,7 +137,7 @@ Query functions can be sync or async.
140137

141138
??? question "Can I make a failed query try again?"
142139

143-
Yes, a `#!python use_mutation` can be re-performed by calling `#!python reset()` on your `#!python use_mutation` instance.
140+
Yes, `#!python use_mutation` can be re-executed by calling `#!python reset()` on your `#!python use_mutation` instance.
144141

145142
For example, take a look at `#!python reset_event` below.
146143

@@ -190,14 +187,15 @@ Mutation functions can be sync or async.
190187

191188
| Name | Type | Description | Default |
192189
| --- | --- | --- | --- |
193-
| `#!python mutation` | `#!python Callable[_Params, bool | None]` | A callable that performs Django ORM create, update, or delete functionality. If this function returns `#!python False`, then your `#!python refetch` function will not be used. | N/A |
194-
| `#!python refetch` | `#!python Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A query function (the function you provide to your `#!python use_query` hook) or a sequence of query functions that need a `refetch` if the mutation succeeds. This is useful for refreshing data after a mutation has been performed. | `#!python None` |
190+
| `#!python mutation` | `#!python Callable[FuncParams, bool | None] | Callable[FuncParams, Awaitable[bool | None]]` | A callable that performs Django ORM create, update, or delete functionality. If this function returns `#!python False`, then your `#!python refetch` function will not be used. | N/A |
191+
| `#!python thread_sensitive` | `#!python bool` | Whether to run the mutation in thread sensitive mode. This mode only applies to sync mutation functions, and is turned on by default due to Django ORM limitations. | `#!python True` |
192+
| `#!python refetch` | `#!python Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A query function (the function you provide to your `#!python use_query` hook) or a sequence of query functions that need a `#!python refetch` if the mutation succeeds. This is useful for refreshing data after a mutation has been performed. | `#!python None` |
195193

196194
<font size="4">**Returns**</font>
197195

198196
| Type | Description |
199197
| --- | --- |
200-
| `#!python Mutation[_Params]` | An object containing `#!python loading`/`#!python error` states, a `#!python reset` callable that will set `#!python loading`/`#!python error` states to defaults, and a `#!python execute` callable that will run the query. |
198+
| `#!python Mutation[FuncParams]` | An object containing `#!python loading`/`#!python error` states, and a `#!python reset` callable that will set `#!python loading`/`#!python error` states to defaults. This object can be called to run the query. |
201199

202200
??? question "How can I provide arguments to my mutation function?"
203201

@@ -211,15 +209,15 @@ Mutation functions can be sync or async.
211209

212210
??? question "How can I customize this hook's behavior?"
213211

214-
This hook accepts a `#!python options: MutationOptions` parameter that can be used to customize behavior.
212+
This hook has several parameters that can be used to customize behavior.
215213

216-
Below are the settings that can be modified via these `#!python MutationOptions`.
214+
Below are examples of values that can be modified.
217215

218216
---
219217

220218
<font size="4">**`#!python thread_sensitive`**</font>
221219

222-
Whether to run your synchronous mutation function in thread-sensitive mode. Thread-sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
220+
Whether to run your synchronous mutation function in thread sensitive mode. Thread sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
223221

224222
This setting only applies to sync query functions, and will be ignored for async functions.
225223

@@ -235,7 +233,7 @@ Mutation functions can be sync or async.
235233

236234
??? question "Can I make a failed mutation try again?"
237235

238-
Yes, a `#!python use_mutation` can be re-performed by calling `#!python reset()` on your `#!python use_mutation` instance.
236+
Yes, `#!python use_mutation` can be re-executed by calling `#!python reset()` on your `#!python use_mutation` instance.
239237

240238
For example, take a look at `#!python reset_event` below.
241239

@@ -257,7 +255,7 @@ Mutation functions can be sync or async.
257255

258256
The example below is a merge of the `#!python use_query` and `#!python use_mutation` examples above with the addition of a `#!python use_mutation(refetch=...)` argument.
259257

260-
Please note that `refetch` will cause all `#!python use_query` hooks that use `#!python get_items` in the current component tree will be refetched.
258+
Please note that `#!python refetch` will cause all `#!python use_query` hooks that use `#!python get_items` in the current component tree will be refetched.
261259

262260
=== "components.py"
263261

0 commit comments

Comments
 (0)