kivy-garden-pyle
pyle is an experimental library aimed at reducing the boilerplate needed to create Kivy bindings without using the Kv language.
For example, suppose you want a function that adds a solid-color background to a specific Widget instance and provides a way to revert it.
Your code might look like this:
from contextlib import ExitStack
from kivy.graphics import Color, Rectangle
from kivy.clock import Clock
def add_solid_background(widget, *, color=(1., 1. , 1., .3)):
'''
Adds a solid-color background to widget and returns a function that reverts it.
'''
with ExitStack() as stack:
defer = stack.callback
before = widget.canvas.before
with before:
defer(before.remove, Color(*color))
defer(before.remove, rect := Rectangle(pos=widget.pos, size=widget.size))
def sync_graphics(dt, rect=rect, w=widget):
rect.pos = w.pos
rect.size = w.size
t = Clock.create_trigger(sync_graphics, -1)
defer(t.cancel)
widget.bind(pos=t, size=t)
defer(widget.unbind, pos=t, size=t)
return stack.pop_all().close
revert = add_solid_background(widget)
...
revert()
Now, here is what an equivalent implementation looks like with pyle:
from contextlib import ExitStack
from kivy.graphics import Color, Rectangle
from kivy_garden import pyle
def add_solid_background(widget, *, color=(1., 1. , 1., .3)):
with ExitStack() as stack:
defer = stack.callback
before = widget.canvas.before
with before:
defer(before.remove, Color(*color))
defer(before.remove, rect := Rectangle(pos=widget.pos, size=widget.size))
@pyle.throttle_rule
def sync_graphics(dt, rect=rect, w=widget):
rect.pos = w.pos
rect.size = w.size
stack.enter_context(sync_graphics)
return stack.pop_all().close
As you can see, the binding code becomes much cleaner. And of course, if you are using an async library, you may want to implement the feature as an async function, tying the background's lifetime to the coroutine it returns:
from contextlib import ExitStack
import asynckivy as ak
from kivy.graphics import Color, Rectangle
from kivy_garden import pyle
async def enable_solid_background(widget, *, color=(1., 1. , 1., .3)):
'''
Enables a solid-color background for a widget until the returned coroutine is cancelled.
'''
with ExitStack() as stack:
defer = stack.callback
before = widget.canvas.before
with before:
defer(before.remove, Color(*color))
defer(before.remove, rect := Rectangle(pos=widget.pos, size=widget.size))
@pyle.throttle_rule
def sync_graphics(dt, rect=rect, w=widget):
rect.pos = w.pos
rect.size = w.size
stack.enter_context(sync_graphics)
await ak.sleep_forever()
In the examples above, the @pyle.throttle_rule decorator turns sync_graphics into a context manager.
At this point, bindings are not yet enabled; they are enabled only while the context manager is active.
The context manager is not reentrant, but it is reusable:
# error
with sync_graphics:
with sync_graphics:
...
# fine
with sync_graphics:
...
with sync_graphics:
...
How does it know which Kivy properties should be observed?
It analyzes the signature and bytecode of the given function.
When the function has a pre-filled argument that is an instance of EventDispatcher,
any of its Kivy properties accessed via LOAD_ATTR will be observed.
Only the following types of "pre-filled" arguments are subjected to observation:
- default values like in the
sync_graphicsexample (w=widget) - values bound with
functools.partial selfarguments bound viaobj.instance_method
Any other way of filling arguments will not create bindings. For more details, please see test_detect_dependencies.py.
Tested on
- CPython 3.11 + Kivy 2.3.1
- CPython 3.12 + Kivy 2.3.1
- CPython 3.13 + Kivy 2.3.1
- CPython 3.14 + Kivy
be90b9f
Metadata
Release files for kivy-garden-pyle 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kivy_garden_pyle-0.1.0.tar.gz | 4.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kivy_garden_pyle-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 8.9 kB
Release files / kivy_garden_pyle-0.1.0.tar.gz
| Download URL | kivy_garden_pyle-0.1.0.tar.gz |
|---|---|
| Size | 4.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3a66d1fdef225ad4f5e0d3642b231137ba2ffaddaca0b00853c58606c4baee16
|
|
BLAKE2b-256 checksum How to use checksums |
1635b1ece23ae3315408b05986297be1ee9f37fb009830a38265c564dda834c5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.
Transparency logRelease files / kivy_garden_pyle-0.1.0-py3-none-any.whl
| Download URL | kivy_garden_pyle-0.1.0-py3-none-any.whl |
|---|---|
| Size | 4.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b8a3118434125b93c93f795cd2e376f54b9905c16d46ad0ee68602e51c04684d
|
|
BLAKE2b-256 checksum How to use checksums |
fd45b5fd9e6df43b9062043608dbc710dbfa2dc23fa3597a251e2d849a580fac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.
Transparency log