FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
pythonnative/src/pythonnative/component.py at main · pythonnative/pythonnative · GitHub
pythonnative
/
pythonnative
Public
Notifications
You must be signed in to change notification settings
Fork
23
Star
131
Code
Issues
11
Pull requests
4
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
pythonnative
/
src
/
pythonnative
/
component.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
320 lines (257 loc) · 11.7 KB
Breadcrumbs
pythonnative
/
src
/
pythonnative
/
component.py
Copy path
File metadata and controls
320 lines (257 loc) · 11.7 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
"""The ``@component`` decorator and the [`Component`][pythonnative.Component] type.
A component is a plain Python function that takes props and returns a
[`Node`][pythonnative.element.Node]: an [`Element`][pythonnative.Element],
a list of elements, or ``None``. Decorating it with
[`@component`][pythonnative.component.component] turns it into a
[`Component`][pythonnative.Component]: a callable that *describes* a
render (it returns an ``Element`` whose ``type`` is the component)
instead of performing one. The reconciler invokes the function body
later, with hook state installed.
The decorator preserves the function's signature for type checkers via
:class:`typing.ParamSpec`, so ``Greeting(nme="x")`` is a static error
and editors autocomplete props from the function definition.
Children
--------
Children are positional. A component that accepts children declares a
``*children: pn.Node`` parameter, exactly like the built-in containers:
```python
@pn.component
def Card(*children: pn.Node, title: str = "") -> pn.Node:
return pn.Column(pn.Text(title, style=pn.style(bold=True)), *children)
Card(pn.Text("body"), title="Hello")
```
Positional arguments to a component *without* ``*children`` bind to its
positional parameters, so ``Greeting("World")`` works for
``def Greeting(name: str)``.
Keys
----
``key`` identifies an element, not a prop, so a component's signature
can't declare it. Key a component's element with
[`Element.with_key`][pythonnative.element.Element.with_key], which type-checks
at every call site:
```python
pn.Column(*(Row(item).with_key(item.id) for item in items))
```
Passing ``key=`` to a user component raises ``TypeError``; built-in
factories accept ``key=`` directly.
"""
from
__future__
import
annotations
import
functools
import
inspect
from
typing
import
Any
,
Awaitable
,
Callable
,
Dict
,
Generic
,
Mapping
,
Optional
,
ParamSpec
,
Union
,
overload
from
.
import
diagnostics
from
.
element
import
Element
,
Node
__all__
=
[
"Component"
,
"RenderFn"
,
"component"
,
"memo"
,
"is_component"
]
P
=
ParamSpec
(
"P"
)
RenderFn
=
Callable
[
P
,
Union
[
Node
,
Awaitable
[
Node
]]]
"""A component body: returns a [`Node`][pythonnative.element.Node], or awaits one when ``async def``."""
def
_hook_signature
(
fn
:
Callable
[...,
Any
])
->
Optional
[
tuple
[
str
, ...]]:
"""Fast Refresh's hook fingerprint of ``fn``, or ``None`` without the refresh module.
Release bundles omit ``pythonnative.refresh`` (and ship bytecode, which
has no source to fingerprint), so the import is lazy, optional, and
attempted once.
"""
global
_refresh_signature
if
_refresh_signature
is
None
:
try
:
from
.
refresh
import
hook_signature
except
ImportError
:
_refresh_signature
=
False
else
:
_refresh_signature
=
hook_signature
return
_refresh_signature
(
fn
)
if
callable
(
_refresh_signature
)
else
None
_refresh_signature
:
Union
[
None
,
bool
,
Callable
[[
Callable
[...,
Any
]],
Optional
[
tuple
[
str
, ...]]]]
=
None
class
Component
(
Generic
[
P
]):
"""A render-function wrapped by [`@component`][pythonnative.component.component].
Calling a ``Component`` does not run the function; it returns an
[`Element`][pythonnative.Element] describing the call so the
reconciler can mount it, preserve its hook state across renders,
and re-run it when its state or props change.
Attributes:
fn: The original render function.
display_name: Name shown in diagnostics and dev tooling
(defaults to ``fn.__name__``).
memoized: Whether [`memo`][pythonnative.memo] was applied, in
which case the reconciler skips re-rendering this component
when its props are shallowly equal to the previous render.
accepts_children: Whether ``fn`` declares ``*children``.
"""
__slots__
=
(
"fn"
,
"display_name"
,
"memoized"
,
"props_equal"
,
"accepts_children"
,
"_signature"
,
"_keyed_builtin"
,
"_is_async"
,
"refresh_signature"
,
"__wrapped__"
,
# ``functools.update_wrapper`` copies ``__module__`` and
# ``__qualname__`` from the render function so Fast Refresh can
# find a component's replacement by module and name; those two
# names can't be slots (they conflict with the class attributes),
# so instances keep a ``__dict__`` for them.
"__dict__"
,
)
def
__init__
(
self
,
fn
:
RenderFn
[
P
],
*
,
display_name
:
Optional
[
str
]
=
None
,
_keyed_builtin
:
bool
=
False
)
->
None
:
if
isinstance
(
fn
,
Component
):
raise
TypeError
(
f"
{
fn
!r
}
is already a component; remove the duplicate @component"
)
sig
=
inspect
.
signature
(
fn
)
accepts_children
=
False
for
name
,
param
in
sig
.
parameters
.
items
():
if
param
.
kind
is
inspect
.
Parameter
.
VAR_POSITIONAL
:
accepts_children
=
True
if
name
==
"key"
and
not
_keyed_builtin
:
raise
TypeError
(
f"
{
getattr
(
fn
,
'__qualname__'
,
fn
)!r
}
declares a 'key' parameter, but 'key' names an element's "
"identity, not a prop. Rename the parameter; key a rendered element with .with_key(...)."
)
self
.
refresh_signature
=
_hook_signature
(
fn
)
self
.
fn
=
fn
self
.
display_name
:
str
=
display_name
or
str
(
getattr
(
fn
,
"__name__"
,
"Component"
))
self
.
memoized
=
False
self
.
props_equal
:
Optional
[
Callable
[[
Mapping
[
str
,
Any
],
Mapping
[
str
,
Any
]],
bool
]]
=
None
self
.
accepts_children
=
accepts_children
self
.
_signature
=
sig
self
.
_keyed_builtin
=
_keyed_builtin
self
.
_is_async
=
inspect
.
iscoroutinefunction
(
fn
)
self
.
__wrapped__
=
fn
functools
.
update_wrapper
(
self
,
fn
,
updated
=
())
# ------------------------------------------------------------------
# Element construction
# ------------------------------------------------------------------
def
__call__
(
self
,
*
args
:
P
.
args
,
**
kwargs
:
P
.
kwargs
)
->
Element
:
"""Describe a render of this component with the given props."""
key
:
Optional
[
str
]
=
None
if
"key"
in
kwargs
:
if
not
self
.
_keyed_builtin
:
raise
TypeError
(
f"
{
self
.
display_name
}
() got key=...; components don't take a key prop. "
f"Use
{
self
.
display_name
}
(...).with_key(key) instead."
)
raw
=
kwargs
.
pop
(
"key"
)
key
=
None
if
raw
is
None
else
str
(
raw
)
bound
=
self
.
_signature
.
bind
(
*
args
,
**
kwargs
)
bound
.
apply_defaults
()
children
:
tuple
[
Any
, ...]
=
()
props
:
Dict
[
str
,
Any
]
=
{}
for
name
,
value
in
bound
.
arguments
.
items
():
kind
=
self
.
_signature
.
parameters
[
name
].
kind
if
kind
is
inspect
.
Parameter
.
VAR_POSITIONAL
:
children
=
value
else
:
props
[
name
]
=
value
props
.
pop
(
"key"
,
None
)
if
diagnostics
.
is_dev
()
and
not
self
.
_keyed_builtin
:
from
.
prop_checks
import
check_props
check_props
(
self
,
props
)
return
Element
(
self
,
props
,
children
,
key
=
key
)
# ------------------------------------------------------------------
# Rendering (used by the reconciler)
# ------------------------------------------------------------------
def
render
(
self
,
element
:
Element
)
->
Any
:
"""Invoke the render function for ``element``.
Children stored on the element are passed positionally; props
are passed by keyword. Returns whatever the function returned
(an element, a list, ``None``, or a coroutine for ``async def``
bodies).
"""
arguments
=
dict
(
element
.
props
)
for
name
,
param
in
self
.
_signature
.
parameters
.
items
():
if
param
.
kind
is
inspect
.
Parameter
.
VAR_POSITIONAL
:
arguments
[
name
]
=
element
.
children
bound
=
inspect
.
BoundArguments
(
self
.
_signature
,
arguments
)
return
self
.
fn
(
*
bound
.
args
,
**
bound
.
kwargs
)
@
property
def
is_async
(
self
)
->
bool
:
"""Whether the render function is an ``async def``."""
return
self
.
_is_async
def
__repr__
(
self
)
->
str
:
flags
=
" memo"
if
self
.
memoized
else
""
return
f"<Component
{
self
.
display_name
}
{
flags
}
>"
def
__get__
(
self
,
instance
:
Any
,
owner
:
Any
=
None
)
->
"Component[P]"
:
# Components stored on classes (rare) should not bind ``self``.
return
self
def
component
(
fn
:
RenderFn
[
P
])
->
Component
[
P
]:
"""Turn a render function into a [`Component`][pythonnative.Component].
The decorated function may use hooks (``use_state``, ``use_effect``,
etc.) and returns an [`Element`][pythonnative.Element] tree, a list
of elements, or ``None``. Each call site creates an independent
component instance with its own hook state.
Args:
fn: The render function. May be ``async def``; the body is then
driven by the reconciler and suspends on pending awaits (see
[`Suspense`][pythonnative.Suspense]).
Returns:
A ``Component`` whose call signature mirrors ``fn``.
Example:
```python
import pythonnative as pn
@pn.component
def Greeting(name: str = "World"):
return pn.Text(f"Hello, {name}!")
```
"""
return
Component
(
fn
)
@
overload
def
memo
(
target
:
Component
[
P
],
*
,
equal
:
Optional
[
Callable
[[
Mapping
[
str
,
Any
],
Mapping
[
str
,
Any
]],
bool
]]
=
None
)
->
Component
[
P
]: ...
@
overload
def
memo
(
target
:
None
=
None
,
*
,
equal
:
Optional
[
Callable
[[
Mapping
[
str
,
Any
],
Mapping
[
str
,
Any
]],
bool
]]
=
None
)
->
Callable
[[
Component
[
P
]],
Component
[
P
]]: ...
def
memo
(
target
:
Optional
[
Component
[
P
]]
=
None
,
*
,
equal
:
Optional
[
Callable
[[
Mapping
[
str
,
Any
],
Mapping
[
str
,
Any
]],
bool
]]
=
None
,
)
->
Any
:
"""Skip a component's render when its props haven't changed.
Apply on top of ``@component``. When the reconciler re-renders the
parent tree, a memoized child is skipped (its previously-rendered
subtree is reused) iff its props and children are equal to the
previous render and none of its own state setters fired. Props are
compared shallowly by default: callables by identity, everything
else by ``==``.
Pair with [`use_callback`][pythonnative.use_callback] when passing
callbacks as props, otherwise a fresh closure defeats the memo.
Args:
target: A [`Component`][pythonnative.Component].
equal: Optional custom comparator ``(old_props, new_props) ->
bool`` replacing the shallow comparison.
Returns:
The same component, marked for memoization (or a decorator when
called with keyword arguments only).
Example:
```python
@pn.memo
@pn.component
def ExpensiveRow(label: str):
...
@pn.memo(equal=lambda a, b: a["id"] == b["id"])
@pn.component
def Row(id: int, extra: dict):
...
```
"""
def
apply
(
comp
:
Component
[
P
])
->
Component
[
P
]:
if
not
isinstance
(
comp
,
Component
):
raise
TypeError
(
"@memo must wrap a @component; write @memo above @component"
)
comp
.
memoized
=
True
comp
.
props_equal
=
equal
return
comp
if
target
is
None
:
return
apply
return
apply
(
target
)
def
builtin_component
(
fn
:
RenderFn
[
P
])
->
Component
[
P
]:
"""Framework-internal ``@component`` whose signature may declare ``key``.
Built-in factories implemented as components (``FlatList``,
``SectionList``) keep a typed ``key=`` parameter like every other
built-in factory. The key becomes the element's identity and never
reaches the render function.
"""
return
Component
(
fn
,
_keyed_builtin
=
True
)
def
is_component
(
obj
:
Any
)
->
bool
:
"""Return whether ``obj`` is a [`Component`][pythonnative.Component]."""
return
isinstance
(
obj
,
Component
)
Back
|
FazBrowse Home
|
New Git URL