/usr/share/pyshared/kivy/interactive.py is in python-kivy 1.7.2-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 | '''
Interactive launcher
====================
.. versionadded:: 1.3.0
The :class:`InteractiveLauncher` provides a user-friendly python shell interface
to an :class:`App` so that it can be prototyped and debugged interactively.
.. note::
The Kivy API intends for some functions to only be run once or before the
main EventLoop has started. Methods that can normally be called during the
course of an application will work as intended, but specifically overriding
methods such as :meth:`on_touch` dynamically leads to trouble.
Creating an InteractiveLauncher
-------------------------------
Take you existing subclass of :class:`App` (this can be production code) and
pass an instance to the :class:`InteractiveLauncher` constructor.::
from kivy.interactive import InteractiveLauncher
from kivy.app import App
from kivy.uix.button import Button
class MyApp(App):
def build(self):
return Button(test='Hello Shell')
interactiveLauncher = InteractiveLauncher(MyApp()).run()
The script will return, allowing an interpreter shell to continue running and
inspection or modification of the :class:`App` can be done safely through the
InteractiveLauncher instance or the provided SafeMembrane class instances.
Interactive Development
-----------------------
IPython provides a fast way to learn the kivy API. The :class:`App` instance
itself, all of it's attributes, including methods and the entire widget tree,
can be quickly listed by using the '.' operator and pressing 'tab.' Try this
code in an Ipython shell.::
from kivy.interactive import InteractiveLauncher
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
with self.canvas:
Color(1, 1, 0)
d = 30.
Ellipse(pos=(touch.x - d/2, touch.y - d/2), size=(d, d))
class TestApp(App):
def build(self):
return Widget()
i = InteractiveLauncher(TestApp())
i.run()
i. # press 'tab' to list attributes of the app
i.root. # press 'tab' to list attributes of the root widget
# App is boring. Attach a new widget!
i.root.add_widget(MyPaintWidget())
i.safeIn()
# The application is now blocked.
# Click on the screen several times.
i.safeOut()
# The clicks will show up now
# Erase artwork and start over
i.root.canvas.clear()
.. note::
All of the proxies used in the module store their referent in the
:attr:`_ref` attribute, which can be accessed directly if needed, such as
for getting doc strings. :func:`help` and :func:`type` will access the
proxy, not its referent.
Directly Pausing the Application
--------------------------------
Both :class:`InteractiveLauncher` and :class:`SafeMembrane` hold internal
references to :class:`EventLoop`'s 'safe' and 'confirmed'
:class:`threading.Event` objects. You can use their safing methods to control
the application manually.
:meth:`Interactive.safeIn()` will cause the applicaiton to pause and
:meth:`InteractiveLauncher.safeOut()` will allow a paused application
to continue running. This is potentially useful for scripting actions into
functions that need the screen to update etc.
.. note::
The pausing is implemented via :meth:`kivy.clock.Clock.schedule_once` and
occurs before the start of each frame.
Adding Attributes Dynamically
-----------------------------
.. note::
This module uses threading and object proxies to encapsulate the running
:class:`App`. Deadlocks and memory corruption can occur if making direct
references inside the thread without going through the provided proxy(s).
The :class:`InteractiveLauncher` can have attributes added to it exactly like a
normal object, and if these were created from outside the membrane, they will
not be threadsafe because the external references to them in the python
interpreter do not go through InteractiveLauncher's membrane behavior, inherited
from SafeMembrane.
To threadsafe these external referencess, simply assign them to SafeMembrane
instances of themselves like so::
from kivy.interactive import SafeMembrane
interactiveLauncher.attribute = myNewObject
# myNewObject is unsafe
myNewObject = SafeMembrane(myNewObject)
# myNewObject is now safe. Call at will.
myNewObject.method()
TODO
====
Unit tests, an example, and more understanding of which methods are safe in a
running application would be nice. All three would be excellent.
Could be re-written with a context-manager style, ie::
with safe:
foo()
Any use cases besides compacting code?
'''
__all__ = ('SafeMembrane', 'InteractiveLauncher')
from kivy.app import App
from kivy.base import EventLoop
from kivy.clock import Clock
from threading import Thread, Event
def safeWait(dt):
EventLoop.confirmed.set()
EventLoop.safe.wait()
EventLoop.confirmed.clear()
def unwrap(ob):
while type(ob) == SafeMembrane:
ob = ob._ref
return ob
class SafeMembrane(object):
'''
This help is for a proxy object. Did you want help on the proxy's referent
instead? Try using help(<instance>._ref)
Threadsafe proxy that also returns attributes as new thread-safe objects
and makes thread-safe method calls, preventing thread-unsafe objects
from leaking into the user's environment.
'''
__slots__ = ('_ref', 'safe', 'confirmed')
def __init__(self, ob, *args, **kwargs):
self.confirmed = EventLoop.confirmed
self.safe = EventLoop.safe
self._ref = ob
def safeIn(self):
self.safe.clear()
Clock.schedule_once(safeWait, -1)
self.confirmed.wait()
def safeOut(self):
self.safe.set()
def isMethod(self, fn):
return type(fn) is type(self.isMethod)
# Everything from this point on is just a series of thread-safing proxy
# methods that make calls against _ref and threadsafe whenever data will be
# written to or if a method will be called. SafeMembrane instances should
# be unwrapped whenever passing them into the thread
#use type() to determine if an object is a SafeMembrane while debugging
def __repr__(self):
return self._ref.__repr__()
def __call__(self, *args, **kw):
self.safeIn()
args = map(unwrap, args)
for k in kw.keys():
kw[k] = unwrap(kw[k])
r = self._ref(*args, **kw)
self.safeOut()
if r is not None:
return SafeMembrane(r)
def __getattribute__(self, attr, oga=object.__getattribute__):
if attr.startswith('__') or attr == '_ref':
subject = oga(self, '_ref')
if attr == '_ref':
return subject
return getattr(subject, attr)
return oga(self, attr)
def __getattr__(self, attr, oga=object.__getattribute__):
r = getattr(oga(self, '_ref'), attr)
return SafeMembrane(r)
def __setattr__(self, attr, val, osa=object.__setattr__):
if (attr == '_ref'
or hasattr(type(self), attr) and not attr.startswith('__')):
osa(self, attr, val)
else:
self.safeIn()
val = unwrap(val)
setattr(self._ref, attr, val)
self.safeOut()
def __delattr__(self, attr, oda=object.__delattr__):
self.safeIn()
delattr(self._ref, attr)
self.safeOut()
def __nonzero__(self):
return bool(self._ref)
def __getitem__(self, arg):
return SafeMembrane(self._ref[arg])
def __setitem__(self, arg, val):
self.safeIn()
val = unwrap(val)
self._ref[arg] = val
self.safeOut()
def __delitem__(self, arg):
self.safeIn()
del self._ref[arg]
self.safeOut()
def __getslice__(self, i, j):
return SafeMembrane(self._ref[i:j])
def __setslice__(self, i, j, val):
self.safeIn()
val = unwrap(val)
self._ref[i:j] = val
self.safeOut()
def __delslice__(self, i, j):
self.safeIn()
del self._ref[i:j]
self.safeOut()
def __enter__(self, *args, **kwargs):
self.safeIn()
self._ref.__enter__(*args, **kwargs)
def __exit__(self, *args, **kwargs):
self._ref.__exit__(*args, **kwargs)
self.safeOut()
class InteractiveLauncher(SafeMembrane):
'''
Proxy to an application instance that launches it in a thread and
then returns and acts as a proxy to the application in the thread
'''
__slots__ = ('_ref', 'safe', 'confirmed', 'thread', 'app')
def __init__(self, app=None, *args, **kwargs):
if app is None:
app = App()
EventLoop.safe = Event()
self.safe = EventLoop.safe
self.safe.set()
EventLoop.confirmed = Event()
self.confirmed = EventLoop.confirmed
self.app = app
def startApp(app=app, *args, **kwargs):
app.run(*args, **kwargs)
self.thread = Thread(target=startApp, *args, **kwargs)
def run(self):
self.thread.start()
#Proxy behavior starts after this is set. Before this point, attaching
#widgets etc can only be done through the Launcher's app attribute
self._ref = self.app
def stop(self):
EventLoop.quit = True
self.thread.join()
#Act like the app instance even before _ref is set
def __repr__(self):
return self.app.__repr__()
|