Coverage for custom_components/supernotify/__init__.py: 99%

194 statements  

« prev     ^ index     » next       coverage.py v7.6.8, created at 2024-12-28 14:21 +0000

1"""The SuperNotification integration""" 

2 

3from dataclasses import dataclass, field 

4from enum import StrEnum 

5 

6import voluptuous as vol 

7from homeassistant.components.notify import PLATFORM_SCHEMA 

8from homeassistant.const import ( 

9 ATTR_DOMAIN, 

10 ATTR_SERVICE, 

11 CONF_ACTION, 

12 CONF_ALIAS, 

13 CONF_CONDITION, 

14 CONF_DEFAULT, 

15 CONF_DESCRIPTION, 

16 CONF_EMAIL, 

17 CONF_ENABLED, 

18 CONF_ENTITIES, 

19 CONF_ICON, 

20 CONF_ID, 

21 CONF_NAME, 

22 CONF_PLATFORM, 

23 CONF_TARGET, 

24 CONF_URL, 

25 STATE_HOME, 

26 STATE_NOT_HOME, 

27 Platform, 

28) 

29from homeassistant.helpers import config_validation as cv 

30 

31from custom_components.supernotify.common import format_timestamp as format_timestamp 

32 

33DOMAIN = "supernotify" 

34 

35PLATFORMS = [Platform.NOTIFY] 

36TEMPLATE_DIR = "/config/templates/supernotify" 

37MEDIA_DIR = "supernotify/media" 

38 

39CONF_ACTIONS = "actions" 

40CONF_TITLE = "title" 

41CONF_URI = "uri" 

42CONF_RECIPIENTS = "recipients" 

43CONF_TEMPLATE_PATH = "template_path" 

44CONF_MEDIA_PATH = "media_path" 

45CONF_HOUSEKEEPING = "housekeeping" 

46CONF_HOUSEKEEPING_TIME = "housekeeping_time" 

47CONF_ARCHIVE_PATH = "archive_path" 

48CONF_ARCHIVE = "archive" 

49CONF_ARCHIVE_DAYS = "archive_days" 

50CONF_TEMPLATE = "template" 

51CONF_LINKS = "links" 

52CONF_PERSON = "person" 

53CONF_METHOD = "method" 

54CONF_METHODS = "methods" 

55CONF_DELIVERY = "delivery" 

56CONF_SELECTION = "selection" 

57 

58CONF_DATA: str = "data" 

59CONF_OPTIONS: str = "options" 

60CONF_MOBILE: str = "mobile" 

61CONF_NOTIFY: str = "notify" 

62CONF_NOTIFY_ACTION: str = "notify_action" 

63CONF_PHONE_NUMBER: str = "phone_number" 

64CONF_PRIORITY: str = "priority" 

65CONF_OCCUPANCY: str = "occupancy" 

66CONF_SCENARIOS: str = "scenarios" 

67CONF_MANUFACTURER: str = "manufacturer" 

68CONF_DEVICE_TRACKER: str = "device_tracker" 

69CONF_DEVICE_NAME: str = "device_name" 

70CONF_DEVICE_LABELS: str = "device_labels" 

71CONF_MODEL: str = "model" 

72CONF_MESSAGE: str = "message" 

73CONF_TARGETS_REQUIRED: str = "targets_required" 

74CONF_MOBILE_DEVICES: str = "mobile_devices" 

75CONF_MOBILE_DISCOVERY: str = "mobile_discovery" 

76CONF_ACTION_TEMPLATE: str = "action_template" 

77CONF_ACTION_GROUPS: str = "action_groups" 

78CONF_TITLE_TEMPLATE: str = "title_template" 

79CONF_DELIVERY_SELECTION: str = "delivery_selection" 

80CONF_MEDIA: str = "media" 

81CONF_CAMERA: str = "camera" 

82CONF_CLIP_URL: str = "clip_url" 

83CONF_SNAPSHOT_URL: str = "snapshot_url" 

84CONF_PTZ_DELAY: str = "ptz_delay" 

85CONF_PTZ_METHOD: str = "ptz_method" 

86CONF_PTZ_PRESET_DEFAULT: str = "ptz_default_preset" 

87CONF_ALT_CAMERA: str = "alt_camera" 

88CONF_CAMERAS: str = "cameras" 

89 

90OCCUPANCY_ANY_IN = "any_in" 

91OCCUPANCY_ANY_OUT = "any_out" 

92OCCUPANCY_ALL_IN = "all_in" 

93OCCUPANCY_ALL = "all" 

94OCCUPANCY_NONE = "none" 

95OCCUPANCY_ALL_OUT = "all_out" 

96OCCUPANCY_ONLY_IN = "only_in" 

97OCCUPANCY_ONLY_OUT = "only_out" 

98 

99ATTR_PRIORITY = "priority" 

100ATTR_ACTION = "action" 

101ATTR_SCENARIOS_CONSTRAIN = "require_scenarios" 

102ATTR_SCENARIOS_APPLY = "apply_scenarios" 

103ATTR_DELIVERY = "delivery" 

104ATTR_DEFAULT = "default" 

105ATTR_NOTIFICATION_ID = "notification_id" 

106ATTR_DELIVERY_SELECTION = "delivery_selection" 

107ATTR_RECIPIENTS = "recipients" 

108ATTR_DATA = "data" 

109ATTR_MEDIA = "media" 

110ATTR_TITLE = "title" 

111ATTR_MEDIA_SNAPSHOT_URL = "snapshot_url" 

112ATTR_MEDIA_CAMERA_ENTITY_ID = "camera_entity_id" 

113ATTR_MEDIA_CAMERA_DELAY = "camera_delay" 

114ATTR_MEDIA_CAMERA_PTZ_PRESET = "camera_ptz_preset" 

115ATTR_MEDIA_CLIP_URL = "clip_url" 

116ATTR_ACTION_GROUPS = "action_groups" 

117CONF_ACTION_GROUP_NAMES = "action_groups" 

118ATTR_ACTION_CATEGORY = "action_category" 

119ATTR_ACTION_URL = "action_url" 

120ATTR_ACTION_URL_TITLE = "action_url_title" 

121ATTR_MESSAGE_HTML = "message_html" 

122ATTR_JPEG_FLAGS = "jpeg_flags" 

123ATTR_TIMESTAMP = "timestamp" 

124ATTR_DEBUG = "debug" 

125ATTR_ACTIONS = "actions" 

126ATTR_USER_ID = "user_id" 

127 

128DELIVERY_SELECTION_IMPLICIT = "implicit" 

129DELIVERY_SELECTION_EXPLICIT = "explicit" 

130DELIVERY_SELECTION_FIXED = "fixed" 

131 

132DELIVERY_SELECTION_VALUES = [DELIVERY_SELECTION_EXPLICIT, DELIVERY_SELECTION_FIXED, DELIVERY_SELECTION_IMPLICIT] 

133PTZ_METHOD_ONVIF = "onvif" 

134PTZ_METHOD_FRIGATE = "frigate" 

135PTZ_METHOD_VALUES = [PTZ_METHOD_ONVIF, PTZ_METHOD_FRIGATE] 

136 

137SELECTION_FALLBACK_ON_ERROR = "fallback_on_error" 

138SELECTION_FALLBACK = "fallback" 

139SELECTION_BY_SCENARIO = "scenario" 

140SELECTION_DEFAULT = "default" 

141SELECTION_VALUES = [SELECTION_FALLBACK_ON_ERROR, SELECTION_BY_SCENARIO, SELECTION_DEFAULT, SELECTION_FALLBACK] 

142 

143OCCUPANCY_VALUES = [ 

144 OCCUPANCY_ALL_IN, 

145 OCCUPANCY_ALL_OUT, 

146 OCCUPANCY_ANY_IN, 

147 OCCUPANCY_ANY_OUT, 

148 OCCUPANCY_ONLY_IN, 

149 OCCUPANCY_ONLY_OUT, 

150 OCCUPANCY_ALL, 

151 OCCUPANCY_NONE, 

152] 

153 

154PRIORITY_CRITICAL = "critical" 

155PRIORITY_HIGH = "high" 

156PRIORITY_MEDIUM = "medium" 

157PRIORITY_LOW = "low" 

158 

159PRIORITY_VALUES = [PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_CRITICAL] 

160METHOD_SMS = "sms" 

161METHOD_EMAIL = "email" 

162METHOD_ALEXA = "alexa" 

163METHOD_MOBILE_PUSH = "mobile_push" 

164METHOD_MEDIA = "media" 

165METHOD_CHIME = "chime" 

166METHOD_GENERIC = "generic" 

167METHOD_PERSISTENT = "persistent" 

168METHOD_VALUES = [ 

169 METHOD_SMS, 

170 METHOD_ALEXA, 

171 METHOD_MOBILE_PUSH, 

172 METHOD_CHIME, 

173 METHOD_EMAIL, 

174 METHOD_MEDIA, 

175 METHOD_PERSISTENT, 

176 METHOD_GENERIC, 

177] 

178 

179SCENARIO_DEFAULT = "DEFAULT" 

180SCENARIO_NULL = "NULL" 

181 

182RESERVED_DELIVERY_NAMES = ["ALL"] 

183RESERVED_SCENARIO_NAMES = [SCENARIO_DEFAULT, SCENARIO_NULL] 

184RESERVED_DATA_KEYS = [ATTR_DOMAIN, ATTR_SERVICE, "action"] 

185 

186CONF_DUPE_CHECK = "dupe_check" 

187CONF_DUPE_POLICY = "dupe_policy" 

188CONF_TTL = "ttl" 

189CONF_SIZE = "size" 

190ATTR_DUPE_POLICY_MTSLP = "dupe_policy_message_title_same_or_lower_priority" 

191ATTR_DUPE_POLICY_NONE = "dupe_policy_none" 

192 

193DATA_SCHEMA = vol.Schema({vol.NotIn(RESERVED_DATA_KEYS): vol.Any(str, int, bool, float, dict, list)}) 

194MOBILE_DEVICE_SCHEMA = vol.Schema({ 

195 vol.Optional(CONF_MANUFACTURER): cv.string, 

196 vol.Optional(CONF_MODEL): cv.string, 

197 vol.Optional(CONF_NOTIFY_ACTION): cv.string, 

198 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id, 

199}) 

200NOTIFICATION_DUPE_SCHEMA = vol.Schema({ 

201 vol.Optional(CONF_TTL): cv.positive_int, 

202 vol.Optional(CONF_SIZE, default=100): cv.positive_int, # type: ignore 

203 vol.Optional(CONF_DUPE_POLICY, default=ATTR_DUPE_POLICY_MTSLP): vol.In( # type: ignore 

204 [ATTR_DUPE_POLICY_MTSLP, ATTR_DUPE_POLICY_NONE] 

205 ), # type: ignore 

206}) 

207DELIVERY_CUSTOMIZE_SCHEMA = vol.Schema({ 

208 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

209 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

210 vol.Optional(CONF_ENABLED, default=True): cv.boolean, # type: ignore 

211 vol.Optional(CONF_DATA): DATA_SCHEMA, 

212}) 

213LINK_SCHEMA = vol.Schema({ 

214 vol.Optional(CONF_ID): cv.string, 

215 vol.Required(CONF_URL): cv.url, 

216 vol.Optional(CONF_ICON): cv.icon, 

217 vol.Required(CONF_DESCRIPTION): cv.string, 

218 vol.Optional(CONF_NAME): cv.string, 

219}) 

220METHOD_DEFAULTS_SCHEMA = vol.Schema({ 

221 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

222 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

223 vol.Optional(CONF_ACTION): cv.service, 

224 vol.Optional(CONF_TARGETS_REQUIRED): cv.boolean, 

225 vol.Optional(CONF_OPTIONS, default=dict): dict, # type: ignore 

226 vol.Optional(CONF_DATA): DATA_SCHEMA, 

227}) 

228RECIPIENT_SCHEMA = vol.Schema({ 

229 vol.Required(CONF_PERSON): cv.entity_id, 

230 vol.Optional(CONF_ALIAS): cv.string, 

231 vol.Optional(CONF_EMAIL): cv.string, 

232 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

233 vol.Optional(CONF_PHONE_NUMBER): cv.string, 

234 vol.Optional(CONF_MOBILE_DISCOVERY, default=True): cv.boolean, # type: ignore 

235 vol.Optional(CONF_MOBILE_DEVICES, default=list): vol.All(cv.ensure_list, [MOBILE_DEVICE_SCHEMA]), # type: ignore 

236 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_CUSTOMIZE_SCHEMA}, # type: ignore 

237}) 

238CAMERA_SCHEMA = vol.Schema({ 

239 vol.Required(CONF_CAMERA): cv.entity_id, 

240 vol.Optional(CONF_ALT_CAMERA): vol.All(cv.ensure_list, [cv.entity_id]), 

241 vol.Optional(CONF_ALIAS): cv.string, 

242 vol.Optional(CONF_URL): cv.url, 

243 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id, 

244 vol.Optional(CONF_PTZ_PRESET_DEFAULT, default=1): vol.Any(cv.positive_int, cv.string), # type: ignore 

245 vol.Optional(CONF_PTZ_DELAY, default=0): int, # type: ignore 

246 vol.Optional(CONF_PTZ_METHOD, default=PTZ_METHOD_ONVIF): vol.In(PTZ_METHOD_VALUES), # type: ignore 

247}) 

248MEDIA_SCHEMA = vol.Schema({ 

249 vol.Optional(ATTR_MEDIA_CAMERA_ENTITY_ID): cv.entity_id, 

250 vol.Optional(ATTR_MEDIA_CAMERA_DELAY, default=0): int, # type: ignore 

251 vol.Optional(ATTR_MEDIA_CAMERA_PTZ_PRESET): vol.Any(cv.positive_int, cv.string), 

252 # URL fragments allowed 

253 vol.Optional(ATTR_MEDIA_CLIP_URL): vol.Any(cv.url, cv.string), 

254 vol.Optional(ATTR_MEDIA_SNAPSHOT_URL): vol.Any(cv.url, cv.string), 

255 vol.Optional(ATTR_JPEG_FLAGS): dict, 

256}) 

257DELIVERY_SCHEMA = vol.Schema({ 

258 vol.Optional(CONF_ALIAS): cv.string, 

259 vol.Required(CONF_METHOD): vol.In(METHOD_VALUES), 

260 vol.Optional(CONF_ACTION): cv.service, # previously 'service:' 

261 vol.Optional(CONF_PLATFORM): cv.string, 

262 vol.Optional(CONF_TEMPLATE): cv.string, 

263 vol.Optional(CONF_DEFAULT, default=False): cv.boolean, # type: ignore 

264 vol.Optional(CONF_SELECTION, default=[SELECTION_DEFAULT]): vol.All( # type: ignore 

265 cv.ensure_list, [vol.In(SELECTION_VALUES)] 

266 ), 

267 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

268 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

269 vol.Optional(CONF_MESSAGE): vol.Any(None, cv.string), 

270 vol.Optional(CONF_TITLE): vol.Any(None, cv.string), 

271 vol.Optional(CONF_DATA): DATA_SCHEMA, 

272 vol.Optional(CONF_ENABLED, default=True): cv.boolean, # type: ignore # type: ignore 

273 vol.Optional(CONF_OPTIONS, default=dict): dict, # type: ignore 

274 vol.Optional(CONF_PRIORITY, default=PRIORITY_VALUES): vol.All( # type: ignore 

275 cv.ensure_list, [vol.In(PRIORITY_VALUES)] 

276 ), 

277 vol.Optional(CONF_OCCUPANCY, default=OCCUPANCY_ALL): vol.In(OCCUPANCY_VALUES), # type: ignore 

278 vol.Optional(CONF_CONDITION): cv.CONDITION_SCHEMA, 

279}) 

280 

281SCENARIO_SCHEMA = vol.Schema({ 

282 vol.Optional(CONF_ALIAS): cv.string, 

283 vol.Optional(CONF_CONDITION): cv.CONDITION_SCHEMA, 

284 vol.Optional(CONF_MEDIA): MEDIA_SCHEMA, 

285 vol.Optional(CONF_ACTION_GROUP_NAMES, default=[]): vol.All(cv.ensure_list, [cv.string]), # type: ignore 

286 vol.Optional(CONF_DELIVERY_SELECTION): vol.In(DELIVERY_SELECTION_VALUES), 

287 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}, # type: ignore 

288}) 

289ACTION_CALL_SCHEMA = vol.Schema( 

290 { 

291 vol.Optional(ATTR_ACTION): cv.string, 

292 vol.Optional(ATTR_TITLE): cv.string, 

293 vol.Optional(ATTR_ACTION_CATEGORY): cv.string, 

294 vol.Optional(ATTR_ACTION_URL): cv.url, 

295 vol.Optional(ATTR_ACTION_URL_TITLE): cv.string, 

296 }, 

297 extra=vol.ALLOW_EXTRA, 

298) 

299ACTION_SCHEMA = vol.Schema( 

300 { 

301 vol.Exclusive(CONF_ACTION, CONF_ACTION_TEMPLATE): cv.string, 

302 vol.Exclusive(CONF_TITLE, CONF_TITLE_TEMPLATE): cv.string, 

303 vol.Optional(CONF_URI): cv.url, 

304 vol.Optional(CONF_ICON): cv.string, 

305 }, 

306 extra=vol.ALLOW_EXTRA, 

307) 

308 

309 

310ARCHIVE_SCHEMA = vol.Schema({ 

311 vol.Optional(CONF_ARCHIVE_PATH): cv.path, 

312 vol.Optional(CONF_ENABLED, default=False): cv.boolean, # type: ignore 

313 vol.Optional(CONF_ARCHIVE_DAYS, default=3): cv.positive_int, # type: ignore 

314}) 

315 

316HOUSEKEEPING_SCHEMA = vol.Schema({ 

317 vol.Optional(CONF_HOUSEKEEPING_TIME, default="00:00:01"): cv.time, # type: ignore 

318}) 

319 

320PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 

321 vol.Optional(CONF_TEMPLATE_PATH, default=TEMPLATE_DIR): cv.path, # type: ignore 

322 vol.Optional(CONF_MEDIA_PATH, default=MEDIA_DIR): cv.path, # type: ignore 

323 vol.Optional(CONF_ARCHIVE, default={CONF_ENABLED: False}): ARCHIVE_SCHEMA, # type: ignore 

324 vol.Optional(CONF_HOUSEKEEPING, default=dict): HOUSEKEEPING_SCHEMA, # type: ignore 

325 vol.Optional(CONF_DUPE_CHECK, default=dict): NOTIFICATION_DUPE_SCHEMA, # type: ignore 

326 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_SCHEMA}, # type: ignore 

327 vol.Optional(CONF_ACTION_GROUPS, default=dict): {cv.string: [ACTION_SCHEMA]}, # type: ignore 

328 vol.Optional(CONF_RECIPIENTS, default=list): vol.All(cv.ensure_list, [RECIPIENT_SCHEMA]), # type: ignore # type: ignore 

329 vol.Optional(CONF_LINKS, default=list): vol.All(cv.ensure_list, [LINK_SCHEMA]), # type: ignore 

330 vol.Optional(CONF_SCENARIOS, default=dict): {cv.string: SCENARIO_SCHEMA}, # type: ignore 

331 vol.Optional(CONF_METHODS, default=dict): {cv.string: METHOD_DEFAULTS_SCHEMA}, # type: ignore 

332 vol.Optional(CONF_CAMERAS, default=list): vol.All(cv.ensure_list, [CAMERA_SCHEMA]), # type: ignore 

333}) 

334 

335ACTION_DATA_SCHEMA = vol.Schema( 

336 { 

337 vol.Optional(ATTR_DELIVERY): vol.Any(cv.string, [cv.string], {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}), 

338 vol.Optional(ATTR_PRIORITY): vol.In(PRIORITY_VALUES), 

339 vol.Optional(ATTR_SCENARIOS_CONSTRAIN): vol.All(cv.ensure_list, [cv.string]), 

340 vol.Optional(ATTR_SCENARIOS_APPLY): vol.All(cv.ensure_list, [cv.string]), 

341 vol.Optional(ATTR_DELIVERY_SELECTION): vol.In(DELIVERY_SELECTION_VALUES), 

342 vol.Optional(ATTR_RECIPIENTS): vol.All(cv.ensure_list, [cv.entity_id]), 

343 vol.Optional(ATTR_MEDIA): MEDIA_SCHEMA, 

344 vol.Optional(ATTR_MESSAGE_HTML): cv.string, 

345 vol.Optional(ATTR_ACTION_GROUPS, default=[]): vol.All(cv.ensure_list, [cv.string]), # type: ignore 

346 vol.Optional(ATTR_ACTIONS, default=[]): vol.All(cv.ensure_list, [ACTION_CALL_SCHEMA]), # type: ignore 

347 vol.Optional(ATTR_DEBUG, default=False): cv.boolean, # type: ignore 

348 vol.Optional(ATTR_DATA): vol.Any(None, DATA_SCHEMA), 

349 }, 

350 extra=vol.ALLOW_EXTRA, # allow other data, e.g. the android/ios mobile push 

351) 

352 

353STRICT_ACTION_DATA_SCHEMA = ACTION_DATA_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) 

354 

355 

356class TargetType(StrEnum): 

357 pass 

358 

359 

360class GlobalTargetType(TargetType): 

361 NONCRITICAL = "NONCRITICAL" 

362 EVERYTHING = "EVERYTHING" 

363 

364 

365class RecipientType(StrEnum): 

366 USER = "USER" 

367 EVERYONE = "EVERYONE" 

368 

369 

370class QualifiedTargetType(TargetType): 

371 METHOD = "METHOD" 

372 DELIVERY = "DELIVERY" 

373 CAMERA = "CAMERA" 

374 PRIORITY = "PRIORITY" 

375 ACTION = "ACTION" 

376 

377 

378class CommandType(StrEnum): 

379 SNOOZE = "SNOOZE" 

380 SILENCE = "SILENCE" 

381 NORMAL = "NORMAL" 

382 

383 

384@dataclass 

385class ConditionVariables: 

386 """Variables presented to all condition evaluations 

387 

388 Attributes 

389 ---------- 

390 applied_scenarios (list[str]): Scenarios that have been applied 

391 required_scenarios (list[str]): Scenarios that must be applied 

392 notification_priority (str): Priority of the notification 

393 notification_message (str): Message of the notification 

394 notification_title (str): Title of the notification 

395 occupancy (list[str]): List of occupancy scenarios 

396 

397 """ 

398 

399 applied_scenarios: list[str] = field(default_factory=list) 

400 required_scenarios: list[str] = field(default_factory=list) 

401 notification_priority: str = PRIORITY_MEDIUM 

402 notification_message: str = "" 

403 notification_title: str = "" 

404 occupancy: list[str] = field(default_factory=list) 

405 

406 def __init__( 

407 self, 

408 applied_scenarios: list[str] | None = None, 

409 required_scenarios: list[str] | None = None, 

410 delivery_priority: str | None = PRIORITY_MEDIUM, 

411 occupiers: dict | None = None, 

412 message: str | None = None, 

413 title: str | None = None, 

414 ) -> None: 

415 occupiers = occupiers or {} 

416 self.occupancy = [] 

417 if not occupiers.get(STATE_NOT_HOME) and occupiers.get(STATE_HOME): 

418 self.occupancy.append("ALL_HOME") 

419 elif occupiers.get(STATE_NOT_HOME) and not occupiers.get(STATE_HOME): 

420 self.occupancy.append("ALL_AWAY") 

421 if len(occupiers.get(STATE_HOME, [])) == 1: 

422 self.occupancy.extend(["LONE_HOME", "SOME_HOME"]) 

423 elif len(occupiers.get(STATE_HOME, [])) > 1 and occupiers.get(STATE_NOT_HOME): 

424 self.occupancy.extend(["MULTI_HOME", "SOME_HOME"]) 

425 self.applied_scenarios = applied_scenarios or [] 

426 self.required_scenarios = required_scenarios or [] 

427 self.notification_priority = delivery_priority or PRIORITY_MEDIUM 

428 self.notification_message = message or "" 

429 self.notification_title = title or ""