Skip to content

Commit 6662afb

Browse files
committed
Sequence fix
1 parent 93ff6e4 commit 6662afb

File tree

2 files changed

+63
-46
lines changed

2 files changed

+63
-46
lines changed

appdaemon/sequences.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async def add_sequences(self, sequences):
5555
if sequence_namespace is not None:
5656
attributes.update({"namespace": sequence_namespace})
5757

58-
if not self.AD.state.entity_exists("rules", entity):
58+
if not await self.AD.state.entity_exists("rules", entity):
5959
# it doesn't exist so add it
6060
await self.AD.state.add_entity(
6161
"rules",
@@ -83,7 +83,7 @@ async def remove_sequences(self, sequences):
8383
await self.cancel_sequence(sequence)
8484
await self.AD.state.remove_entity("rules", "sequence.{}".format(sequence))
8585

86-
async def run_sequence(self, _name: str, namespace: str, sequence: str | list[str]):
86+
async def run_sequence(self, _name, namespace, sequence):
8787
if isinstance(sequence, str):
8888
if "." in sequence:
8989
# the entity given
@@ -128,13 +128,13 @@ async def cancel_sequence(self, sequence):
128128
self.AD.futures.cancel_futures(name)
129129
await self.AD.state.set_state("_sequences", "rules", entity_id, state="idle")
130130

131-
async def prep_sequence(self, _name: str, namespace: str, sequence: str | list[str]):
131+
async def prep_sequence(self, _name, namespace, sequence):
132132
ephemeral_entity = False
133133
loop = False
134134

135135
if isinstance(sequence, str):
136136
entity_id = sequence
137-
if self.AD.state.entity_exists("rules", entity_id) is False:
137+
if await self.AD.state.entity_exists("rules", entity_id) is False:
138138
self.logger.warning(
139139
'Unknown sequence "%s" in run_sequence()', sequence)
140140
return None
@@ -148,33 +148,20 @@ async def prep_sequence(self, _name: str, namespace: str, sequence: str | list[s
148148
#
149149
# Assume it's a list with the actual commands in it
150150
#
151-
assert isinstance(sequence, list) and all(
152-
isinstance(s, str) for s in sequence)
153151
entity_id = "sequence.{}".format(uuid.uuid4().hex)
154152
# Create an ephemeral entity for it
155153
ephemeral_entity = True
156154

157-
await self.AD.state.add_entity(
158-
namespace="rules",
159-
entity=entity_id,
160-
state="idle",
161-
attributes={"steps": sequence}
162-
)
155+
await self.AD.state.add_entity("rules", entity_id, "idle", attributes={"steps": sequence})
163156

164157
seq = sequence
165158
ns = namespace
166159

167160
coro = await self.do_steps(ns, entity_id, seq, ephemeral_entity, loop)
168161
return coro
169162

170-
async def do_steps(self,
171-
namespace: str,
172-
entity_id: str,
173-
seq: str | list[str],
174-
ephemeral_entity: bool = False,
175-
loop: bool = False):
163+
async def do_steps(self, namespace, entity_id, seq, ephemeral_entity, loop):
176164
await self.AD.state.set_state("_sequences", "rules", entity_id, state="active")
177-
178165
try:
179166
while True:
180167
steps = copy.deepcopy(seq)
@@ -216,7 +203,7 @@ async def do_steps(self,
216203
# now we create the wait entity object
217204
entity_object = Entity(
218205
self.logger, self.AD, name, ns, wait_entity)
219-
if not entity_object.exists():
206+
if not await entity_object.exists():
220207
self.logger.warning(
221208
f"Waiting for an entity {wait_entity}, in sequence {
222209
entity_name}, that doesn't exist"
@@ -232,6 +219,7 @@ async def do_steps(self,
232219

233220
else:
234221
domain, service = str.split(command, "/")
222+
# parameters["__name"] = entity_id
235223
loop_step = parameters.pop("loop_step", None)
236224
params = copy.deepcopy(parameters)
237225
await self.AD.services.call_service(ns, domain, service, entity_id, params)
@@ -242,12 +230,14 @@ async def do_steps(self,
242230

243231
if loop is not True:
244232
break
233+
245234
except Exception:
246235
self.logger.error("-" * 60)
247-
self.logger.error("Unexpected error when attempting do_steps()")
236+
self.logger.error("Unexpected error in do_steps()")
248237
self.logger.error("-" * 60)
249238
self.logger.error(traceback.format_exc())
250239
self.logger.error("-" * 60)
240+
251241
finally:
252242
await self.AD.state.set_state("_sequences", "rules", entity_id, state="idle")
253243

@@ -272,7 +262,13 @@ async def loop_step(self, namespace: str, command: str, parameters: dict, loop_s
272262

273263
except Exception:
274264
self.logger.error("-" * 60)
275-
self.logger.error("Unexpected error when attempting to loop step")
265+
self.logger.error("Unexpected error in loop_step()")
276266
self.logger.error("-" * 60)
277267
self.logger.error(traceback.format_exc())
278268
self.logger.error("-" * 60)
269+
270+
#
271+
# Placeholder for constraints
272+
#
273+
def list_constraints(self):
274+
return []

appdaemon/services.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ def register_service(self, namespace: str, domain: str, service: str, callback:
8585
name = kwargs.get("__name")
8686
# first we confirm if the namespace exists
8787
if name and namespace not in self.AD.state.state:
88-
raise NamespaceException(f"Namespace {namespace}, doesn't exist")
88+
raise NamespaceException(
89+
f"Namespace {namespace}, doesn't exist")
8990

9091
elif not callable(callback):
91-
raise ValueError(f"The given callback {callback} is not a callable function")
92+
raise ValueError(f"The given callback {
93+
callback} is not a callable function")
9294

9395
if namespace not in self.services:
9496
self.services[namespace] = {}
@@ -99,27 +101,32 @@ def register_service(self, namespace: str, domain: str, service: str, callback:
99101
if service in self.services[namespace][domain]:
100102
# there was a service already registered before
101103
# so if a different app, we ask to deregister first
102-
service_app = self.services[namespace][domain][service].get("__name")
104+
service_app = self.services[namespace][domain][service].get(
105+
"__name")
103106
if service_app and service_app != name:
104107
self.logger.warning(
105-
f"This service '{domain}/{service}' already registered to a different app '{service_app}', and so cannot be registered to {name}. Do deregister from app first"
108+
f"This service '{domain}/{service}' already registered to a different app '{
109+
service_app}', and so cannot be registered to {name}. Do deregister from app first"
106110
)
107111
return
108112

109-
self.services[namespace][domain][service] = {"callback": callback, "__name": name, **kwargs}
113+
self.services[namespace][domain][service] = {
114+
"callback": callback, "__name": name, **kwargs}
110115

111116
if __silent is False:
112117
data = {
113118
"event_type": "service_registered",
114119
"data": {"namespace": namespace, "domain": domain, "service": service},
115120
}
116-
self.AD.loop.create_task(self.AD.events.process_event(namespace, data))
121+
self.AD.loop.create_task(
122+
self.AD.events.process_event(namespace, data))
117123

118124
if name:
119125
if name not in self.app_registered_services:
120126
self.app_registered_services[name] = set()
121127

122-
self.app_registered_services[name].add(f"{namespace}:{domain}:{service}")
128+
self.app_registered_services[name].add(
129+
f"{namespace}:{domain}:{service}")
123130

124131
def deregister_service(self, namespace: str, domain: str, service: str, __name: str) -> bool:
125132
"""Used to unregister a service"""
@@ -133,12 +140,14 @@ def deregister_service(self, namespace: str, domain: str, service: str, __name:
133140
)
134141

135142
if __name not in self.app_registered_services:
136-
raise ValueError(f"The given App {__name} has no services registered")
143+
raise ValueError(f"The given App {
144+
__name} has no services registered")
137145

138146
app_service = f"{namespace}:{domain}:{service}"
139147

140148
if app_service not in self.app_registered_services[__name]:
141-
raise ValueError(f"The given App {__name} doesn't have the given service registered it")
149+
raise ValueError(f"The given App {
150+
__name} doesn't have the given service registered it")
142151

143152
# if it gets here, then time to deregister
144153
with self.services_lock:
@@ -149,7 +158,8 @@ def deregister_service(self, namespace: str, domain: str, service: str, __name:
149158
"event_type": "service_deregistered",
150159
"data": {"namespace": namespace, "domain": domain, "service": service, "app": __name},
151160
}
152-
self.AD.loop.create_task(self.AD.events.process_event(namespace, data))
161+
self.AD.loop.create_task(
162+
self.AD.events.process_event(namespace, data))
153163

154164
# now check if that domain is empty
155165
# if it is, remove it also
@@ -192,29 +202,37 @@ def list_services(self, ns: str = "global") -> list[dict[str, str]]:
192202
]
193203

194204
async def call_service(
195-
self,
196-
namespace: str,
197-
domain: str,
198-
service: str,
199-
name: str | None = None,
200-
data: dict[str, Any] | None = None, # Don't expand with **data
201-
) -> Any:
205+
self,
206+
namespace: str,
207+
domain: str,
208+
service: str,
209+
name: str | None = None,
210+
data: dict[str, Any] | None = None, # Don't expand with **data
211+
) -> Any:
202212
self.logger.debug(
203213
"call_service: namespace=%s domain=%s service=%s data=%s",
204214
namespace,
205215
domain,
206216
service,
207217
data,
208218
)
219+
220+
# data can be None, later on we assume it is not!
221+
if data is None:
222+
data = {}
223+
209224
with self.services_lock:
210225
if namespace not in self.services:
211-
raise NamespaceException(f"Unknown namespace {namespace} in call_service from {name}")
226+
raise NamespaceException(f"Unknown namespace {
227+
namespace} in call_service from {name}")
212228

213229
if domain not in self.services[namespace]:
214-
raise DomainException(f"Unknown domain ({namespace}/{domain}) in call_service from {name}")
230+
raise DomainException(
231+
f"Unknown domain ({namespace}/{domain}) in call_service from {name}")
215232

216233
if service not in self.services[namespace][domain]:
217-
raise ServiceException(f"Unknown service ({namespace}/{domain}/{service}) in call_service from {name}")
234+
raise ServiceException(
235+
f"Unknown service ({namespace}/{domain}/{service}) in call_service from {name}")
218236

219237
# If we have namespace in data it's an override for the domain of the eventual service call, as distinct
220238
# from the namespace the call itself is executed from. e.g. set_state() is in the AppDaemon namespace but
@@ -230,9 +248,10 @@ async def call_service(
230248
match isasync := service_def.pop("__async", 'auto'):
231249
case 'auto':
232250
# Remove any wrappers from the funcref before determining if it's async or not
233-
isasync = asyncio.iscoroutinefunction(utils.unwrapped(funcref))
251+
isasync = asyncio.iscoroutinefunction(
252+
utils.unwrapped(funcref))
234253
case bool():
235-
pass # isasync already set as a bool from above
254+
pass # isasync already set as a bool from above
236255
case _:
237256
raise TypeError(f'Invalid __async type: {isasync}')
238257

@@ -247,9 +266,11 @@ async def call_service(
247266
else:
248267
# It's not a coroutine, run it in an executor
249268
if use_dictionary_unpacking:
250-
coro = utils.run_in_executor(self, funcref, ns, domain, service, **data)
269+
coro = utils.run_in_executor(
270+
self, funcref, ns, domain, service, **data)
251271
else:
252-
coro = utils.run_in_executor(self, funcref, ns, domain, service, data)
272+
coro = utils.run_in_executor(
273+
self, funcref, ns, domain, service, data)
253274

254275
@utils.warning_decorator(error_text=f"Unexpected error calling service {ns}/{domain}/{service}")
255276
async def safe_service(self: 'Services'):

0 commit comments

Comments
 (0)