Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New use_query and use_mutation API #241

Merged
merged 6 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,21 @@ Using the following categories, list your changes in this order:

## [Unreleased]

- Nothing (yet)!
### Changed

- New syntax for `use_query` and `use_mutation` hooks. Here's a quick comparison of the changes:

```python
query = use_query(QueryOptions(thread_sensitive=True), get_items, value=123456, foo="bar") # Old
query = use_query(get_items, {"value":12356, "foo":"bar"}, thread_sensitive=True) # New

mutation = use_mutation(MutationOptions(thread_sensitive=True), remove_item) # Old
mutation = use_mutation(remove_item, thread_sensitive=True) # New
```

### Removed

- `QueryOptions` and `MutationOptions` have been removed. Their values are now passed direct into the hook.

## [3.8.1] - 2024-05-07

Expand Down
9 changes: 3 additions & 6 deletions docs/examples/python/django-query-postprocessor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from example.models import TodoItem
from reactpy import component
from reactpy_django.hooks import use_query
from reactpy_django.types import QueryOptions
from reactpy_django.utils import django_query_postprocessor


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

@component
def todo_list():
# These `QueryOptions` are functionally equivalent to ReactPy-Django's default values
# These postprocessor options are functionally equivalent to ReactPy-Django's default values
item_query = use_query(
QueryOptions(
postprocessor=django_query_postprocessor,
postprocessor_kwargs={"many_to_many": True, "many_to_one": True},
),
get_items,
postprocessor=django_query_postprocessor,
postprocessor_kwargs={"many_to_many": True, "many_to_one": True},
)

return item_query.data
4 changes: 3 additions & 1 deletion docs/examples/python/use-mutation-query-refetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ def submit_event(event):
elif item_query.error or not item_query.data:
rendered_items = html.h2("Error when loading!")
else:
rendered_items = html.ul(html.li(item, key=item.pk) for item in item_query.data)
rendered_items = html.ul(
html.li(item.text, key=item.pk) for item in item_query.data
)

# Handle all possible mutation states
if item_mutation.loading:
Expand Down
3 changes: 1 addition & 2 deletions docs/examples/python/use-mutation-thread-sensitive.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from reactpy import component, html
from reactpy_django.hooks import use_mutation
from reactpy_django.types import MutationOptions


def execute_thread_safe_mutation(text):
Expand All @@ -11,8 +10,8 @@ def execute_thread_safe_mutation(text):
@component
def my_component():
item_mutation = use_mutation(
MutationOptions(thread_sensitive=False),
execute_thread_safe_mutation,
thread_sensitive=False,
)

def submit_event(event):
Expand Down
9 changes: 2 additions & 7 deletions docs/examples/python/use-query-args.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,11 @@
from reactpy_django.hooks import use_query


def example_query(value: int, other_value: bool = False):
...
def example_query(value: int, other_value: bool = False): ...


@component
def my_component():
query = use_query(
example_query,
123,
other_value=True,
)
query = use_query(example_query, {"value": 123, "other_value": True})

return str(query.data)
7 changes: 2 additions & 5 deletions docs/examples/python/use-query-postprocessor-change.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from reactpy import component
from reactpy_django.hooks import use_query
from reactpy_django.types import QueryOptions


def my_postprocessor(data, example_kwarg=True):
Expand All @@ -18,11 +17,9 @@ def execute_io_intensive_operation():
@component
def my_component():
query = use_query(
QueryOptions(
postprocessor=my_postprocessor,
postprocessor_kwargs={"example_kwarg": False},
),
execute_io_intensive_operation,
postprocessor=my_postprocessor,
postprocessor_kwargs={"example_kwarg": False},
)

if query.loading or query.error:
Expand Down
3 changes: 1 addition & 2 deletions docs/examples/python/use-query-postprocessor-disable.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from reactpy import component
from reactpy_django.hooks import use_query
from reactpy_django.types import QueryOptions


def execute_io_intensive_operation():
Expand All @@ -11,8 +10,8 @@ def execute_io_intensive_operation():
@component
def my_component():
query = use_query(
QueryOptions(postprocessor=None),
execute_io_intensive_operation,
postprocessor=None,
)

if query.loading or query.error:
Expand Down
5 changes: 1 addition & 4 deletions docs/examples/python/use-query-postprocessor-kwargs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from example.models import TodoItem
from reactpy import component
from reactpy_django.hooks import use_query
from reactpy_django.types import QueryOptions


def get_model_with_relationships():
Expand All @@ -17,10 +16,8 @@ def get_model_with_relationships():
@component
def my_component():
query = use_query(
QueryOptions(
postprocessor_kwargs={"many_to_many": False, "many_to_one": False}
),
get_model_with_relationships,
postprocessor_kwargs={"many_to_many": False, "many_to_one": False},
)

if query.loading or query.error or not query.data:
Expand Down
6 changes: 1 addition & 5 deletions docs/examples/python/use-query-thread-sensitive.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from reactpy import component
from reactpy_django.hooks import use_query
from reactpy_django.types import QueryOptions


def execute_thread_safe_operation():
Expand All @@ -10,10 +9,7 @@ def execute_thread_safe_operation():

@component
def my_component():
query = use_query(
QueryOptions(thread_sensitive=False),
execute_thread_safe_operation,
)
query = use_query(execute_thread_safe_operation, thread_sensitive=False)

if query.loading or query.error:
return None
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/python/use-query.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def todo_list():
rendered_items = html.h2("Error when loading!")
else:
rendered_items = html.ul(
[html.li(item, key=item.pk) for item in item_query.data]
[html.li(item.text, key=item.pk) for item in item_query.data]
)

return html.div("Rendered items: ", rendered_items)
2 changes: 1 addition & 1 deletion docs/includes/orm.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ These `#!python SynchronousOnlyOperation` exceptions may be removed in a future

<!--orm-fetch-start-->

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.
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.

<!--orm-fetch-end-->
48 changes: 23 additions & 25 deletions docs/src/reference/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,21 @@ Query functions can be sync or async.

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `#!python options` | `#!python QueryOptions | None` | An optional `#!python QueryOptions` object that can modify how the query is executed. | `#!python None` |
| `#!python query` | `#!python Callable[_Params, _Result | None]` | A callable that returns a Django `#!python Model` or `#!python QuerySet`. | N/A |
| `#!python *args` | `#!python _Params.args` | Positional arguments to pass into `#!python query`. | N/A |
| `#!python **kwargs` | `#!python _Params.kwargs` | Keyword arguments to pass into `#!python query`. | N/A |
| `#!python query` | `#!python Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred]` | A function that executes a query and returns some data. | N/A |
| `#!python kwargs` | `#!python dict[str, Any] | None` | Keyword arguments to passed into the `#!python query` function. | `#!python None` |
| `#!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` |
| `#!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` |
| `#!python postprocessor_kwargs` | `#!python dict[str, Any] | None` | Keyworded arguments passed into the `#!python postprocessor` function. | `#!python None` |

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

| Type | Description |
| --- | --- |
| `#!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. |
| `#!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. |

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

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

=== "components.py"

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

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

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

Below are the settings that can be modified via these `#!python QueryOptions`.
Below are examples of values that can be modified.

---

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

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.
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.

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

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

... 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.
... 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.

=== "components.py"

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

If you wish to create a custom `#!python postprocessor`, you will need to create a callable.

The first argument of `#!python postprocessor` must be the query `#!python data`. All proceeding arguments
are optional `#!python postprocessor_kwargs` (see below). This `#!python postprocessor` must return
the modified `#!python data`.
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`.

=== "components.py"

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

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.

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.
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.

=== "components.py"

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

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

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

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

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

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `#!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 |
| `#!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` |
| `#!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 |
| `#!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` |
| `#!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` |

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

| Type | Description |
| --- | --- |
| `#!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. |
| `#!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. |

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

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

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

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

Below are the settings that can be modified via these `#!python MutationOptions`.
Below are examples of values that can be modified.

---

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

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.
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.

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

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

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

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

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

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

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.

Please note that `refetch` will cause all `#!python use_query` hooks that use `#!python get_items` in the current component tree will be refetched.
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.

=== "components.py"

Expand Down
Loading
Loading