Skip to content

Instantly share code, notes, and snippets.

@clayote
Last active November 14, 2025 05:00
Show Gist options
  • Select an option

  • Save clayote/f1aa49fad1067bf9aee2c3eebf69a383 to your computer and use it in GitHub Desktop.

Select an option

Save clayote/f1aa49fad1067bf9aee2c3eebf69a383 to your computer and use it in GitHub Desktop.
Trying to make my Kivy 3.0.0 app backward-compatible with Kivy 2.3.1
# This happens at import time, before anything else is imported from kivy
from kivy import __version__ as kivy_version_str, parse_kivy_version
if parse_kivy_version(kivy_version_str)[0][0] < 3:
from kivy.properties import BooleanProperty
from kivy.uix import button
def on_state(self, *_):
self.pressed = self.state == "down"
button.ButtonBehavior.pressed = BooleanProperty(False)
button.ButtonBehavior.on_state = on_state
from kivy.uix import togglebutton
def on_state(self, *_):
self.active = self.state == "down"
togglebutton.ToggleButtonBehavior.active = BooleanProperty(False)
togglebutton.ToggleButtonBehavior.on_state = on_state
# this happens when I use a copy of the standard kivy_init fixture in my conftest.py
test setup failed
self = <kivy.lang.builder.BuilderBase object at 0x7e5603918170>
widget = <Screen name='funcs'>
rule = <ParserRuleProperty name='store_name' filename=/home/sanotehu/src/Lisien/elide/elide/stores.kv:244 value="'method'" watched_keys=None>
rootrule = <ParserRule name='<FuncsEdScreen>'>, template_ctx = None
ignored_consts = {'name', 'toggle'}
rule_children = [<elide.util.SelectableRecycleBoxLayout object at 0x7e5582eb5710>, <elide.stores.StoreList object at 0x7e5582ec59b0>, ...e040>, <elide.stores.FunctionNameInput object at 0x7e5582e69c50>, <kivy.uix.label.Label object at 0x7e5582e09630>, ...]
def _apply_rule(self, widget, rule, rootrule, template_ctx=None,
ignored_consts=set(), rule_children=None):
# widget: the current instantiated widget
# rule: the current rule
# rootrule: the current root rule (for children of a rule)
# will collect reference to all the id in children
assert rule not in self.rulectx
self.rulectx[rule] = rctx = {
'ids': {'root': widget.proxy_ref},
'set': [], 'hdl': []}
# extract the context of the rootrule (not rule!)
assert rootrule in self.rulectx
rctx = self.rulectx[rootrule]
# if a template context is passed, put it as "ctx"
if template_ctx is not None:
rctx['ids']['ctx'] = QueryDict(template_ctx)
# if we got an id, put it in the root rule for a later global usage
if rule.id:
# use only the first word as `id` discard the rest.
rule.id = rule.id.split('#', 1)[0].strip()
rctx['ids'][rule.id] = widget.proxy_ref
# set id name as a attribute for root widget so one can in python
# code simply access root_widget.id_name
_ids = dict(rctx['ids'])
_root = _ids.pop('root')
_new_ids = _root.ids
for _key, _value in _ids.items():
if _value == _root:
# skip on self
continue
_new_ids[_key] = _value
_root.ids = _new_ids
# first, ensure that the widget have all the properties used in
# the rule if not, they will be created as ObjectProperty.
rule.create_missing(widget)
# build the widget canvas
if rule.canvas_before:
with widget.canvas.before:
self._build_canvas(widget.canvas.before, widget,
rule.canvas_before, rootrule)
if rule.canvas_root:
with widget.canvas:
self._build_canvas(widget.canvas, widget,
rule.canvas_root, rootrule)
if rule.canvas_after:
with widget.canvas.after:
self._build_canvas(widget.canvas.after, widget,
rule.canvas_after, rootrule)
# create children tree
Factory_get = Factory.get
Factory_is_template = Factory.is_template
for crule in rule.children:
cname = crule.name
if cname in ('canvas', 'canvas.before', 'canvas.after'):
raise ParserException(
crule.ctx, crule.line,
'Canvas instructions added in kv must '
'be declared before child widgets.')
# depending if the child rule is a template or not, we are not
# having the same approach
cls = Factory_get(cname)
if Factory_is_template(cname):
# we got a template, so extract all the properties and
# handlers, and push them in a "ctx" dictionary.
ctx = {}
idmap = copy(global_idmap)
idmap.update({'root': rctx['ids']['root']})
if 'ctx' in rctx['ids']:
idmap.update({'ctx': rctx['ids']['ctx']})
try:
for prule in crule.properties.values():
value = prule.co_value
if type(value) is CodeType:
value = eval(value, idmap)
ctx[prule.name] = value
for prule in crule.handlers:
value = eval(prule.value, idmap)
ctx[prule.name] = value
except Exception as e:
tb = sys.exc_info()[2]
raise BuilderException(
prule.ctx, prule.line,
'{}: {}'.format(e.__class__.__name__, e), cause=tb)
# create the template with an explicit ctx
child = cls(**ctx)
widget.add_widget(child)
# reference it on our root rule context
if crule.id:
rctx['ids'][crule.id] = child
else:
# we got a "normal" rule, construct it manually
# we can't construct it without __no_builder=True, because the
# previous implementation was doing the add_widget() before
# apply(), and so, we could use "self.parent".
child = cls(__no_builder=True)
widget.add_widget(child)
child.apply_class_lang_rules(
root=rctx['ids']['root'], rule_children=rule_children)
self._apply_rule(
child, crule, rootrule, rule_children=rule_children)
if rule_children is not None:
rule_children.append(child)
# append the properties and handlers to our final resolution task
if rule.properties:
rctx['set'].append((widget.proxy_ref,
list(rule.properties.values())))
for key, crule in rule.properties.items():
# clear previously applied rules if asked
if crule.ignore_prev:
Builder.unbind_property(widget, key)
if rule.handlers:
rctx['hdl'].append((widget.proxy_ref, rule.handlers))
# if we are applying another rule that the root one, then it's done for
# us!
if rootrule is not rule:
del self.rulectx[rule]
return
# normally, we can apply a list of properties with a proper context
try:
rule = None
for widget_set, rules in reversed(rctx['set']):
for rule in rules:
assert isinstance(rule, ParserRuleProperty)
key = rule.name
value = rule.co_value
if type(value) is CodeType:
value, bound = create_handler(
widget_set, widget_set, key, value, rule,
rctx['ids'])
# if there's a rule
if (widget_set != widget or bound or
key not in ignored_consts):
setattr(widget_set, key, value)
else:
if (widget_set != widget or
key not in ignored_consts):
setattr(widget_set, key, value)
except Exception as e:
if rule is not None:
tb = sys.exc_info()[2]
raise BuilderException(rule.ctx, rule.line,
'{}: {}'.format(e.__class__.__name__,
e), cause=tb)
raise e
# build handlers
try:
crule = None
for widget_set, rules in rctx['hdl']:
for crule in rules:
assert isinstance(crule, ParserRuleProperty)
assert crule.name.startswith('on_')
key = crule.name
if not widget_set.is_event_type(key):
key = key[3:]
idmap = copy(global_idmap)
idmap.update(rctx['ids'])
idmap['self'] = widget_set.proxy_ref
if not widget_set.fbind(key, custom_callback, crule,
idmap):
> raise AttributeError(key)
E AttributeError: active
../../.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/lang/builder.py:734: AttributeError
During handling of the above exception, another exception occurred:
kivy = None
prefix = '/tmp/pytest-of-sanotehu/pytest-29/test_character_switcher0'
@pytest.fixture
def elide_app(kivy, prefix):
app = make_elide_app(
prefix, immediate_start=True, character_name="physical", workers=0
)
app.leave_game = True
app.config = ConfigParser(None)
app.build_config(app.config)
> Window.add_widget(app.build())
elide/elide/tests/conftest.py:115:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
elide/elide/app.py:389: in build
self.start_game()
elide/elide/app.py:683: in start_game
self._add_screens()
elide/elide/app.py:604: in _add_screens
self.funcs = FuncsEdScreen(name="funcs", toggle=toggler("funcs"))
elide/elide/stores.py:749: in __init__
super().__init__(**kw)
../../.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/uix/relativelayout.py:274: in __init__
super(RelativeLayout, self).__init__(**kw)
../../.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/uix/floatlayout.py:65: in __init__
super(FloatLayout, self).__init__(**kwargs)
../../.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/uix/layout.py:76: in __init__
super(Layout, self).__init__(**kwargs)
../../.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/uix/widget.py:366: in __init__
self.apply_class_lang_rules(
../../.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/uix/widget.py:470: in apply_class_lang_rules
Builder.apply(
../../.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/lang/builder.py:545: in apply
self._apply_rule(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <kivy.lang.builder.BuilderBase object at 0x7e5603918170>
widget = <Screen name='funcs'>
rule = <ParserRuleProperty name='store_name' filename=/home/sanotehu/src/Lisien/elide/elide/stores.kv:244 value="'method'" watched_keys=None>
rootrule = <ParserRule name='<FuncsEdScreen>'>, template_ctx = None
ignored_consts = {'name', 'toggle'}
rule_children = [<elide.util.SelectableRecycleBoxLayout object at 0x7e5582eb5710>, <elide.stores.StoreList object at 0x7e5582ec59b0>, ...e040>, <elide.stores.FunctionNameInput object at 0x7e5582e69c50>, <kivy.uix.label.Label object at 0x7e5582e09630>, ...]
def _apply_rule(self, widget, rule, rootrule, template_ctx=None,
ignored_consts=set(), rule_children=None):
# widget: the current instantiated widget
# rule: the current rule
# rootrule: the current root rule (for children of a rule)
# will collect reference to all the id in children
assert rule not in self.rulectx
self.rulectx[rule] = rctx = {
'ids': {'root': widget.proxy_ref},
'set': [], 'hdl': []}
# extract the context of the rootrule (not rule!)
assert rootrule in self.rulectx
rctx = self.rulectx[rootrule]
# if a template context is passed, put it as "ctx"
if template_ctx is not None:
rctx['ids']['ctx'] = QueryDict(template_ctx)
# if we got an id, put it in the root rule for a later global usage
if rule.id:
# use only the first word as `id` discard the rest.
rule.id = rule.id.split('#', 1)[0].strip()
rctx['ids'][rule.id] = widget.proxy_ref
# set id name as a attribute for root widget so one can in python
# code simply access root_widget.id_name
_ids = dict(rctx['ids'])
_root = _ids.pop('root')
_new_ids = _root.ids
for _key, _value in _ids.items():
if _value == _root:
# skip on self
continue
_new_ids[_key] = _value
_root.ids = _new_ids
# first, ensure that the widget have all the properties used in
# the rule if not, they will be created as ObjectProperty.
rule.create_missing(widget)
# build the widget canvas
if rule.canvas_before:
with widget.canvas.before:
self._build_canvas(widget.canvas.before, widget,
rule.canvas_before, rootrule)
if rule.canvas_root:
with widget.canvas:
self._build_canvas(widget.canvas, widget,
rule.canvas_root, rootrule)
if rule.canvas_after:
with widget.canvas.after:
self._build_canvas(widget.canvas.after, widget,
rule.canvas_after, rootrule)
# create children tree
Factory_get = Factory.get
Factory_is_template = Factory.is_template
for crule in rule.children:
cname = crule.name
if cname in ('canvas', 'canvas.before', 'canvas.after'):
raise ParserException(
crule.ctx, crule.line,
'Canvas instructions added in kv must '
'be declared before child widgets.')
# depending if the child rule is a template or not, we are not
# having the same approach
cls = Factory_get(cname)
if Factory_is_template(cname):
# we got a template, so extract all the properties and
# handlers, and push them in a "ctx" dictionary.
ctx = {}
idmap = copy(global_idmap)
idmap.update({'root': rctx['ids']['root']})
if 'ctx' in rctx['ids']:
idmap.update({'ctx': rctx['ids']['ctx']})
try:
for prule in crule.properties.values():
value = prule.co_value
if type(value) is CodeType:
value = eval(value, idmap)
ctx[prule.name] = value
for prule in crule.handlers:
value = eval(prule.value, idmap)
ctx[prule.name] = value
except Exception as e:
tb = sys.exc_info()[2]
raise BuilderException(
prule.ctx, prule.line,
'{}: {}'.format(e.__class__.__name__, e), cause=tb)
# create the template with an explicit ctx
child = cls(**ctx)
widget.add_widget(child)
# reference it on our root rule context
if crule.id:
rctx['ids'][crule.id] = child
else:
# we got a "normal" rule, construct it manually
# we can't construct it without __no_builder=True, because the
# previous implementation was doing the add_widget() before
# apply(), and so, we could use "self.parent".
child = cls(__no_builder=True)
widget.add_widget(child)
child.apply_class_lang_rules(
root=rctx['ids']['root'], rule_children=rule_children)
self._apply_rule(
child, crule, rootrule, rule_children=rule_children)
if rule_children is not None:
rule_children.append(child)
# append the properties and handlers to our final resolution task
if rule.properties:
rctx['set'].append((widget.proxy_ref,
list(rule.properties.values())))
for key, crule in rule.properties.items():
# clear previously applied rules if asked
if crule.ignore_prev:
Builder.unbind_property(widget, key)
if rule.handlers:
rctx['hdl'].append((widget.proxy_ref, rule.handlers))
# if we are applying another rule that the root one, then it's done for
# us!
if rootrule is not rule:
del self.rulectx[rule]
return
# normally, we can apply a list of properties with a proper context
try:
rule = None
for widget_set, rules in reversed(rctx['set']):
for rule in rules:
assert isinstance(rule, ParserRuleProperty)
key = rule.name
value = rule.co_value
if type(value) is CodeType:
value, bound = create_handler(
widget_set, widget_set, key, value, rule,
rctx['ids'])
# if there's a rule
if (widget_set != widget or bound or
key not in ignored_consts):
setattr(widget_set, key, value)
else:
if (widget_set != widget or
key not in ignored_consts):
setattr(widget_set, key, value)
except Exception as e:
if rule is not None:
tb = sys.exc_info()[2]
raise BuilderException(rule.ctx, rule.line,
'{}: {}'.format(e.__class__.__name__,
e), cause=tb)
raise e
# build handlers
try:
crule = None
for widget_set, rules in rctx['hdl']:
for crule in rules:
assert isinstance(crule, ParserRuleProperty)
assert crule.name.startswith('on_')
key = crule.name
if not widget_set.is_event_type(key):
key = key[3:]
idmap = copy(global_idmap)
idmap.update(rctx['ids'])
idmap['self'] = widget_set.proxy_ref
if not widget_set.fbind(key, custom_callback, crule,
idmap):
raise AttributeError(key)
# hack for on_parent
if crule.name == 'on_parent':
Factory.Widget.parent.dispatch(widget_set.__self__)
except Exception as e:
if crule is not None:
tb = sys.exc_info()[2]
> raise BuilderException(
crule.ctx, crule.line,
'{}: {}'.format(e.__class__.__name__, e), cause=tb)
E kivy.lang.builder.BuilderException: Parser: File "/home/sanotehu/src/Lisien/elide/elide/stores.kv", line 240:
E ...
E 238: id: method
E 239: text: 'Method'
E >> 240: on_active: methods.save()
E 241: MethodEdBox:
E 242: id: methods
E ...
E AttributeError: active
E File "/home/sanotehu/.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/lang/builder.py", line 734, in _apply_rule
E raise AttributeError(key)
../../.local/share/virtualenv/LiSE312/lib/python3.12/site-packages/kivy/lang/builder.py:741: BuilderException
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment