Skip to content

Commit 7b03761

Browse files
committed
New use_query and use_mutation API
1 parent 2949aa6 commit 7b03761

File tree

4 files changed

+43
-121
lines changed

4 files changed

+43
-121
lines changed

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:

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)

src/reactpy_django/hooks.py

+35-75
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
from reactpy_django.types import (
3030
AsyncMessageReceiver,
3131
AsyncMessageSender,
32+
AsyncPostprocessor,
3233
ConnectionType,
3334
FuncParams,
3435
Inferred,
3536
Mutation,
3637
MutationOptions,
3738
Query,
3839
QueryOptions,
40+
SyncPostprocessor,
3941
UserData,
4042
)
4143
from reactpy_django.utils import generate_obj_name, get_pk
@@ -102,48 +104,39 @@ def use_connection() -> ConnectionType:
102104
return _use_connection()
103105

104106

105-
@overload
106-
def use_query(
107-
options: QueryOptions,
108-
/,
109-
query: Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred],
110-
*args: FuncParams.args,
111-
**kwargs: FuncParams.kwargs,
112-
) -> Query[Inferred]: ...
113-
114-
115-
@overload
116107
def use_query(
117108
query: Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred],
109+
/,
118110
*args: FuncParams.args,
111+
thread_sensitive: bool = True,
112+
postprocessor: AsyncPostprocessor | SyncPostprocessor | None = None,
113+
postprocessor_kwargs: dict[str, Any] | None = None,
119114
**kwargs: FuncParams.kwargs,
120-
) -> Query[Inferred]: ...
121-
122-
123-
def use_query(*args, **kwargs) -> Query[Inferred]:
115+
) -> Query[Inferred]:
124116
"""This hook is used to execute functions in the background and return the result, \
125117
typically to read data the Django ORM.
126118
127119
Args:
128-
options: An optional `QueryOptions` object that can modify how the query is executed.
129120
query: A callable that returns a Django `Model` or `QuerySet`.
130-
*args: Positional arguments to pass into `query`.
131-
132-
Keyword Args:
133-
**kwargs: Keyword arguments to pass into `query`."""
121+
args: Positional arguments to passed into the `query` function.
122+
kwargs: Keyword arguments to passed into the `query` function.
123+
thread_sensitive: Whether to run the query in thread-sensitive mode. \
124+
This setting only applies to sync query functions.
125+
postprocessor: A callable that processes the query result prior to returning it. \
126+
The first argument of postprocessor function must be the query `data`. All \
127+
proceeding arguments are optional `postprocessor_kwargs` (see below). This \
128+
postprocessor function must return the modified `data`. \
129+
\
130+
If unset, `REACTPY_DEFAULT_QUERY_POSTPROCESSOR` is used. By default, this \
131+
is used to prevent Django's lazy query execution and supports `many_to_many` \
132+
and `many_to_one` as `postprocessor_kwargs`.
133+
postprocessor_kwargs: Keyworded arguments passed into the `postprocessor` function.
134+
"""
134135

135136
should_execute, set_should_execute = use_state(True)
136137
data, set_data = use_state(cast(Inferred, None))
137138
loading, set_loading = use_state(True)
138139
error, set_error = use_state(cast(Union[Exception, None], None))
139-
if isinstance(args[0], QueryOptions):
140-
query_options, query, query_args, query_kwargs = _use_query_args_1(
141-
*args, **kwargs
142-
)
143-
else:
144-
query_options, query, query_args, query_kwargs = _use_query_args_2(
145-
*args, **kwargs
146-
)
147140
query_ref = use_ref(query)
148141
if query_ref.current is not query:
149142
raise ValueError(f"Query function changed from {query_ref.current} to {query}.")
@@ -153,24 +146,21 @@ async def execute_query() -> None:
153146
try:
154147
# Run the query
155148
if asyncio.iscoroutinefunction(query):
156-
new_data = await query(*query_args, **query_kwargs)
149+
new_data = await query(*args, **kwargs)
157150
else:
158151
new_data = await database_sync_to_async(
159152
query,
160-
thread_sensitive=query_options.thread_sensitive,
161-
)(*query_args, **query_kwargs)
153+
thread_sensitive=thread_sensitive,
154+
)(*args, **kwargs)
162155

163156
# Run the postprocessor
164-
if query_options.postprocessor:
165-
if asyncio.iscoroutinefunction(query_options.postprocessor):
166-
new_data = await query_options.postprocessor(
167-
new_data, **query_options.postprocessor_kwargs
168-
)
157+
if postprocessor:
158+
if asyncio.iscoroutinefunction(postprocessor):
159+
new_data = await postprocessor(new_data, **postprocessor_kwargs)
169160
else:
170161
new_data = await database_sync_to_async(
171-
query_options.postprocessor,
172-
thread_sensitive=query_options.thread_sensitive,
173-
)(new_data, **query_options.postprocessor_kwargs)
162+
postprocessor, thread_sensitive=thread_sensitive
163+
)(new_data, **postprocessor_kwargs)
174164

175165
# Log any errors and set the error state
176166
except Exception as e:
@@ -216,26 +206,14 @@ def register_refetch_callback() -> Callable[[], None]:
216206
return Query(data, loading, error, refetch)
217207

218208

219-
@overload
220-
def use_mutation(
221-
options: MutationOptions,
222-
mutation: (
223-
Callable[FuncParams, bool | None] | Callable[FuncParams, Awaitable[bool | None]]
224-
),
225-
refetch: Callable[..., Any] | Sequence[Callable[..., Any]] | None = None,
226-
) -> Mutation[FuncParams]: ...
227-
228-
229-
@overload
230209
def use_mutation(
231210
mutation: (
232211
Callable[FuncParams, bool | None] | Callable[FuncParams, Awaitable[bool | None]]
233212
),
213+
/,
214+
thread_sensitive: bool = True,
234215
refetch: Callable[..., Any] | Sequence[Callable[..., Any]] | None = None,
235-
) -> Mutation[FuncParams]: ...
236-
237-
238-
def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
216+
) -> Mutation[FuncParams]:
239217
"""This hook is used to modify data in the background, typically to create/update/delete \
240218
data from the Django ORM.
241219
@@ -246,6 +224,8 @@ def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
246224
mutation: A callable that performs Django ORM create, update, or delete \
247225
functionality. If this function returns `False`, then your `refetch` \
248226
function will not be used.
227+
thread_sensitive: Whether to run the mutation in thread-sensitive mode. \
228+
This setting only applies to sync mutation functions.
249229
refetch: A query function (the function you provide to your `use_query` \
250230
hook) or a sequence of query functions that need a `refetch` if the \
251231
mutation succeeds. This is useful for refreshing data after a mutation \
@@ -254,10 +234,6 @@ def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
254234

255235
loading, set_loading = use_state(False)
256236
error, set_error = use_state(cast(Union[Exception, None], None))
257-
if isinstance(args[0], MutationOptions):
258-
mutation_options, mutation, refetch = _use_mutation_args_1(*args, **kwargs)
259-
else:
260-
mutation_options, mutation, refetch = _use_mutation_args_2(*args, **kwargs)
261237

262238
# The main "running" function for `use_mutation`
263239
async def execute_mutation(exec_args, exec_kwargs) -> None:
@@ -267,7 +243,7 @@ async def execute_mutation(exec_args, exec_kwargs) -> None:
267243
should_refetch = await mutation(*exec_args, **exec_kwargs)
268244
else:
269245
should_refetch = await database_sync_to_async(
270-
mutation, thread_sensitive=mutation_options.thread_sensitive
246+
mutation, thread_sensitive=thread_sensitive
271247
)(*exec_args, **exec_kwargs)
272248

273249
# Log any errors and set the error state
@@ -444,22 +420,6 @@ def use_root_id() -> str:
444420
return scope["reactpy"]["id"]
445421

446422

447-
def _use_query_args_1(options: QueryOptions, /, query: Query, *args, **kwargs):
448-
return options, query, args, kwargs
449-
450-
451-
def _use_query_args_2(query: Query, *args, **kwargs):
452-
return QueryOptions(), query, args, kwargs
453-
454-
455-
def _use_mutation_args_1(options: MutationOptions, mutation: Mutation, refetch=None):
456-
return options, mutation, refetch
457-
458-
459-
def _use_mutation_args_2(mutation, refetch=None):
460-
return MutationOptions(), mutation, refetch
461-
462-
463423
async def _get_user_data(
464424
user: AbstractUser, default_data: None | dict, save_default_data: bool
465425
) -> dict | None:

src/reactpy_django/types.py

+4-44
Original file line numberDiff line numberDiff line change
@@ -52,49 +52,11 @@ def __call__(self, *args: FuncParams.args, **kwargs: FuncParams.kwargs) -> None:
5252

5353

5454
class AsyncPostprocessor(Protocol):
55-
async def __call__(self, data: Any) -> Any:
56-
...
55+
async def __call__(self, data: Any) -> Any: ...
5756

5857

5958
class SyncPostprocessor(Protocol):
60-
def __call__(self, data: Any) -> Any:
61-
...
62-
63-
64-
@dataclass
65-
class QueryOptions:
66-
"""Configuration options that can be provided to `use_query`."""
67-
68-
from reactpy_django.config import REACTPY_DEFAULT_QUERY_POSTPROCESSOR
69-
70-
postprocessor: AsyncPostprocessor | SyncPostprocessor | None = (
71-
REACTPY_DEFAULT_QUERY_POSTPROCESSOR
72-
)
73-
"""A callable that can modify the query `data` after the query has been executed.
74-
75-
The first argument of postprocessor must be the query `data`. All proceeding arguments
76-
are optional `postprocessor_kwargs` (see below). This postprocessor function must return
77-
the modified `data`.
78-
79-
If unset, REACTPY_DEFAULT_QUERY_POSTPROCESSOR is used.
80-
81-
ReactPy's default django_query_postprocessor prevents Django's lazy query execution, and
82-
additionally can be configured via `postprocessor_kwargs` to recursively fetch
83-
`many_to_many` and `many_to_one` fields."""
84-
85-
postprocessor_kwargs: MutableMapping[str, Any] = field(default_factory=lambda: {})
86-
"""Keyworded arguments directly passed into the `postprocessor` for configuration."""
87-
88-
thread_sensitive: bool = True
89-
"""Whether to run the query in thread-sensitive mode. This setting only applies to sync query functions."""
90-
91-
92-
@dataclass
93-
class MutationOptions:
94-
"""Configuration options that can be provided to `use_mutation`."""
95-
96-
thread_sensitive: bool = True
97-
"""Whether to run the mutation in thread-sensitive mode. This setting only applies to sync mutation functions."""
59+
def __call__(self, data: Any) -> Any: ...
9860

9961

10062
@dataclass
@@ -112,10 +74,8 @@ class UserData(NamedTuple):
11274

11375

11476
class AsyncMessageReceiver(Protocol):
115-
async def __call__(self, message: dict) -> None:
116-
...
77+
async def __call__(self, message: dict) -> None: ...
11778

11879

11980
class AsyncMessageSender(Protocol):
120-
async def __call__(self, message: dict) -> None:
121-
...
81+
async def __call__(self, message: dict) -> None: ...

0 commit comments

Comments
 (0)