[SOLVED] [JSR223][Jython]Cryptic error when using multiple rule definitions in class constructor

RPi 3B+, OH S1628, Jython 2.7.0

I’m trying to create rules in my class constructor. It works fine with either the first or the second one, but not both together! I’ve tried everything i can think of (such as verifying that repr(type(self)) is the right object type, and repr(type(self.item_id_on)) and repr(type(self.item_id_mode)) are both strings.

Code:

class MainGateLights(object):
	def __init__(self, bit_position, item_id_on, item_id_mode):
		logprint("gate lights __init__")
		#logprint("mythread init")
		self.stopFlag = Event()
		
		try:
			self.onoff=ir.getItem(item_id_on).getState().intValue()
		except:
			self.onoff = OFF

		try:
			self.mode=ir.getItem(item_id_mode).getState().intValue()
		except:
			self.mode = OFF
		self.bit_position=bit_position
		self.item_id_on=item_id_on
		self.item_id_mode=item_id_mode

## next line is 62
		self.ruleOn=rule("ruleMainGateLightsOn")(
			when("Item {} received update".format(self.item_id_on))
			(self))

## next line is 66
		self.ruleMode=rule("ruleMainGateLightsMode")(
			when("Item {} received update".format(self.item_id_mode))
			(self))

And then the class instantiation:

## next line is 152
main_object = MainGateLights(6,"MainGateLights_On","MainGateLights_Signal")

And finally the error:

2019-07-19 14:13:46.334 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/etc/openhab2/automation/jsr223/python/personal/lights/gate_lights.py': AttributeError: 'NoneType' object has no attribute 'append' in <script> at line number 152

Again if I comment out EITHER the rule definition on line 66, or the one on line 62, and there is no error.

Would appreciate any help in figuring this out. I don’t know enough python to dig myself out yet. :slight_smile:

If you would provide a complete example of the code that reproduces the issue, I will take a look. But more importantly, step way back and explain what you are trying to accomplish, because chances are good that there is another, maybe better, way to do it.

The…

… is likely coming from core.triggers.when at the time the trigger is being added to the function.triggers list. This is a function decorator, so I’m not surprised.

The first thing that came to mind was to not add the rules as attributes…

## next line is 62
		rule("ruleMainGateLightsOn")(
			when("Item {} received update".format(self.item_id_on))
			(self))

## next line is 66
		rule("ruleMainGateLightsMode")(
			when("Item {} received update".format(self.item_id_mode))
			(self))

Here are some related threads…


Hi Scott! Thanks for looking.

I don’t know what that means. Would you care to elaborate?

I distilled it down to a minimal script to show the issue:

Switch Test_Switch_1 "Test Switch 1" <lightbulb> (Bench)
Switch Test_Switch_2 "Test Switch 2" <lightbulb> (Bench)
from core.rules import rule
from core.triggers import when

from core.log import logging, LOG_PREFIX
log = logging.getLogger(LOG_PREFIX + ".ClassTest2")


class EventReceptor(object):
	def __init__(self, item1, item2):
		self.item1 = item1
		self.item2 = item2

		## this fails if both are used together, but works if either is commented out
		rule("EventReceptor1")(
		 	when("Item {} changed".format(self.item1))
		 	(self))
		rule("EventReceptor2")(
		 	when("Item {} changed".format(self.item2))
		 	(self))


		## this works!
		# self.rule=rule("EventReceptor1")(
		#  	when("Item {} changed".format(self.item1))
		# 	 (when("Item {} changed".format(self.item2))
		#  	(self)))


	def __call__(self, event):
		log.debug("Changed: {} is now {}".format(event.itemName,event.getItemState()))
		pass

	
instance=EventReceptor("Test_Switch_1","Test_Switch_2")

What I was trying to do was to have a class register its own rules so that it’s called when certain items change. The name of each item is specified as parameters to the constructor. Having the class register its own rules makes for less repetition and cleaner code once you instantiate more copies.

In my actual implementation, I worked around th problem by simply defining rules with decorators outside of the class in the usual manner (@rule, @when etc…) so my actual code is running fine now (controlling the gate lights), but I posted because I want to learn what went wrong and how I could accomplish the design pattern of having the class register its own rules from within the init function in the future.

Hey, I just thought to try the following, and this works:

from core.rules import rule
from core.triggers import when

from core.log import logging, LOG_PREFIX
log = logging.getLogger(LOG_PREFIX + ".ClassTest2")

class EventReceptor2(object):
	def __init__(self):
		pass

	def __call__(self, event):
		log.debug("CLASS2: {} is now {}".format(event.itemName,event.getItemState()))
		pass

instance2=EventReceptor2()

class EventReceptor(object):
	def __init__(self, item1, item2):
		self.item1 = item1
		self.item2 = item2

		## this works perfectly!
		rule("EventReceptor1")(
		 	when("Item {} changed".format(self.item1))
		 	(self))
		rule("EventReceptor2")(
		 	when("Item {} changed".format(self.item2))
		 	(instance2))

	def __call__(self, event):
		log.debug("Changed: {} is now {}".format(event.itemName,event.getItemState()))
		pass

	
instance=EventReceptor("Test_Switch_1","Test_Switch_2")

The only real difference is that I changed the second (self) to (instance2).
Does that mean [the fact that I tried to have multiple rules call the same class] is actually the problem?
I thought it would be okay since I could still use event.itemName as caller ID.

Also, this works, but the syntax is tricky when you add more triggers because of all the parantheses required.

self.rule=rule("EventReceptor1")(
	when("Item {} changed".format(self.item1))
	(when("Item {} changed".format(self.item2))
	(self)))

I looked at this thread. Unfortunately the original poster just exclaimed “this worked” without actually posting the what the solution was, leaving only the original non-working example, so it didn’t offer any clarity.

I looked at this thread . Unfortunately the original poster just exclaimed “this worked” without actually posting the what the solution was, leaving only the original non-working example, so it didn’t offer any clarity.

I added the complete file to that thread as it is working on my system.

1 Like

Thanks, @erm! That could be very useful.

But, the rule decorator syntax is a lot simpler and easier to use, if it works. Did you have a chance to look at the problem @5iver?

No… I was still looking for something like…

… as I’m still not clear on what you are trying to do and why. I’m glad you got it working by using the decorators the way they were meant to be used though! :wink:

Well… What I was trying to do was to have one piece of code triggered by two items. For example imagine you’re implementing something that controls a light bulb in a jython script. The light bulb can be turned on and off, as well as have a brightness slider. So you have a switch item (on/off) and a number item (brightness).

But, those are implementation details! They don’t matter for the problem description. The reason for not including those is that then invariably someone will answer “why are you doing that when you can just use a Hue!”

I would think it’s more useful to break it down into a canonical example rather than something implementation specific. I was doing one thing today but I’ll use it for something else completely different tomorrow. It’s a programming pattern, it was the first thing I tried – and it didn’t work. What I’d like to find out is why it didn’t work, because i think it should/could have? Am I using the rule decorators wrong?

So… what’s wrong with this code? Why can’t I use the rule decorator more than once pointing to the same class? I would have thought that doing this would result in __call__ getting called when either of the rules are triggered. I don’t know why there’s a conflict.

Not necessarily. A Dimmer Item can accept both OnOff commands as well as Percent commands for brightness.

Yes, you hit the nail on the head, sir. This is exactly why I didn’t want to be specific in what I was trying to do, because I’m not really doing a dimmer, and I didn’t want the problem description to be misleading.

The thing that I’m actually doing does require two separate items.

This is the only bit of info that I can see describing what you are trying to accomplish. This reads to me like you are just trying to create a rule with two triggers, which there is plenty of documentation for.

From what you are trying to do in your classes, it looks like, for reasons unknown, you might be trying to automate the creation of a rule. For that, you should be able to do something like this. But without knowing what you are trying to accomplish, I am just guessing.

def create_rules(rule_name, item1, item2):
    @rule("{}".format(rule_name))
    @when("Item {} changed".format(item1))
    @when("Item {} changed".format(item2))
    def auto_created_rule(event):
        log.debug("Changed: {} is now {}".format(event.itemName, event.itemState))

create_rules("EventReceptor1", "Test_Switch_1", "Test_Switch_2")
1 Like

@leif you aren’t using the decorators correctly. I stumbled on this while looking at spawning rules at runtime (not in a class mind you). You are trying to build the rule on the object self, but you can’t build a rule on a class, you have to build a rule on something callable like a method.

Edit: actually you can build a rule on a class, but you would only be able to build one rule per class instance. Also when building a rule on a class it must have an execute method that is called when the rule is triggered.

Try this instead:

self.ruleOn = rule("ruleMainGateLightsOn")(when("Item {} received update".format(self.item_id_on))(when("Item {} received update".format(self.item_id_mode))(self._call)))

I haven’t tested this but it should work. Note that the object we are handing over to be decorated is the method self._call (change your __call__ to something like _call because using names wrapped in double underscores is reserved for Python built ins). If you want multiple rules to call the same method I don’t think it will work, but you could just make a second method that calls the first with the event object it receives.

Broken down into steps, this is what is happening:

rule_decorator_function = rule("ruleMainGateLightsOn")
add_trigger_1_function = when("Item {} received update".format(self.item_id_on))
add_trigger_2_function = when("Item {} received update".format(self.item_id_mode))

self.ruleOn = self._call
self.ruleOn = add_trigger_1_function(self.ruleOn)
self.ruleOn = add_trigger_2_function(self.ruleOn)
self.ruleOn = rule_decorator_function(self.ruleOn)
1 Like

@leif have you tested my suggestion yet?

Thanks guys! This is exactly what I was looking for.

That is correct. My bad, I didn’t mention that clearly. The reason why I want to automate the creation of rules is pretty simple: I’m a programmer and I like to write organized, easily scalable code that doesn’t repeat itself. Even if I only need one instance of this object right now, it just feels better if it’s scalable. Actually I’d probably save a lot of time if I didn’t have this hangup, because the goals of home automation are quite different from production programming, but old habits die hard.

This is indeed what I discovered – I was trying to understand why that was the case.

Thank you for this, that is very good information!

When you use the decorators to build a rule on a class, it actually expects a class with a method named __call__ and successfully calls that whenever the rules are triggered. That works. Then I tried to add a second rule on the same class, and that’s what failed.

I tried your syntax (passing a class method (self.method1) to the decorator rather than just (self) and that actually didn’t work, it failed with the following error:

2019-07-27 10:28:43.398 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/etc/openhab2/automation/jsr223/python/personal/test.py': AttributeError: 'instancemethod' object has no attribute 'triggers' in <script> at line number 36

Passing a function that’s not part of a class works fine!

So I guess the problem kind of remains… I’m trying to find a programming pattern that lets me automate definition of multiple rules from within a class, and indeed calling separate methods (one for each rule) would be ideal.
There’s absolutely no reason for two rules to call the same function, the only reason why I tried that was that I didn’t know how to tell the rule decorators which callback method of a class to use, other than __call__ which works. I still don’t.

I also tried to follow Scott’s suggestion:

It works BUT it’s not part of a class. There’s no self pointer and no indentation.

But at least now I’m finally asking an accurate question! Sorry about the confusion.
What I’m trying to establish is a programming pattern that lets me create a self-contained python class that defines multiple rules, each with its own handler.

Something like this:


from core.rules import rule
from core.triggers import when

from core.log import logging, LOG_PREFIX
log = logging.getLogger(LOG_PREFIX + ".RuleTest")

class EventReceptor(object):
	def func1(self, event):
		log.debug("FUNC1: {} is now {}".format(event.itemName,event.getItemState()))
		pass
	
	def func2(self, event):
		log.debug("FUNC2: {} is now {}".format(event.itemName,event.getItemState()))
		pass

	def __init__(self, item1, item2):
		self.item1 = item1
		self.item2 = item2

		rule("ItemTrigger1")(
		 	when("Item {} changed".format(self.item1))
		 	(self.func1))
		rule("ItemTrigger2")(
		 	when("Item {} changed".format(self.item2))
		 	(self.func2))

		
instance=EventReceptor("TestSwitch1","TestSwitch2")

I know I’m not using the decorators correctly, obviously. :slight_smile:
Is there a way to use them to do what I’m trying to do (in bold above)?

Another way: Is there a way to get the decorators to pass my “self” pointer into the callback function?

Something to this effect:


from core.rules import rule
from core.triggers import when

from core.log import logging, LOG_PREFIX
log = logging.getLogger(LOG_PREFIX + ".RuleTest")

class EventReceptor(object):
	def __init__(self, item1, item2):
		self.item1 = item1
		self.item2 = item2
		
		self.magic = "12345"

		@rule("{}".format("itemtrigger1"))
		@when("Item {} changed".format(self.item1))
		def auto_created_rule1(event):
			log.debug("[Rule1] Changed: {} is now {}. Magic is {}".format(event.itemName, event.itemState,self.magic))
		
		@rule("{}".format("itemtrigger2"))
		@when("Item {} changed".format(self.item2))
		def auto_created_rule2(event):
			log.debug("[Rule2] Changed: {} is now {}. Magic is {}".format(event.itemName, event.itemState,self.magic))
		
instance=EventReceptor("TestSwitch1","TestSwitch2")


See here

Did you use the when decorator before the rule decorator? That error looks to me like you tried to call rule on a method you hadn’t called when on yet.

Well… I’m not sure how to answer this question. I do know that the syntax works in some circumstances – it depends on what the callee is.

But, the basic problem is that I just don’t understand Python syntax and the rule decorators well enough. I really want to understand this. :slight_smile:

I’m looking at the code for the rule decorator (which you linked to).

The rules module contains some utility functions and a decorator that can:
    1) convert a Jython class into a `SimpleRule`, 
    2) decorate the trigger decorator (@when) to create a `SimpleRule`.

That is very interesting. So there are two distinctly different ways of doing it. I’ll bet the way I’m trying to do it is first method, and that would explain succinctly why I can’t do it twice on one class! Duh.

So… that actually solves it. I understand exactly now why that can never work. And with that, the original thread @5iver linked to, which @erm kindly updated with the solution, solves the problem!
It means not using the decorators at all (in this particular instance), which certainly complicates the code but is scalable. I’m playing with his example now, it does exactly what I was trying to do, defining two different rules with different callback methods that are members of one and the same class!

Ceratinly in most cases the decorators will be a better answer because of their simplicity, but it’s good to have an alternative.

Thanks all so much for helping!

https://www.jython.org/jythonbook/en/1.0/DefiningFunctionsandUsingBuilt-Ins.html

Holy crap, the decorator documentation just made my head spin. This is going to take me a while.

1 Like