Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_xep_0346.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 4b842c1fb686 |
children |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
81 "ps_schema_ui_get", | 81 "ps_schema_ui_get", |
82 ".plugin", | 82 ".plugin", |
83 in_sign="sss", | 83 in_sign="sss", |
84 out_sign="s", | 84 out_sign="s", |
85 method=lambda service, nodeIdentifier, profile_key: self._get_ui_schema( | 85 method=lambda service, nodeIdentifier, profile_key: self._get_ui_schema( |
86 service, nodeIdentifier, default_node=None, profile_key=profile_key), | 86 service, nodeIdentifier, default_node=None, profile_key=profile_key |
87 ), | |
87 async_=True, | 88 async_=True, |
88 ) | 89 ) |
89 host.bridge.add_method( | 90 host.bridge.add_method( |
90 "ps_schema_dict_get", | 91 "ps_schema_dict_get", |
91 ".plugin", | 92 ".plugin", |
136 return SchemaHandler() | 137 return SchemaHandler() |
137 | 138 |
138 def get_application_ns(self, namespace): | 139 def get_application_ns(self, namespace): |
139 """Retrieve application namespace, i.e. namespace without FDP prefix""" | 140 """Retrieve application namespace, i.e. namespace without FDP prefix""" |
140 if namespace.startswith(SUBMITTED_PREFIX): | 141 if namespace.startswith(SUBMITTED_PREFIX): |
141 namespace = namespace[len(SUBMITTED_PREFIX):] | 142 namespace = namespace[len(SUBMITTED_PREFIX) :] |
142 elif namespace.startswith(TEMPLATE_PREFIX): | 143 elif namespace.startswith(TEMPLATE_PREFIX): |
143 namespace = namespace[len(TEMPLATE_PREFIX):] | 144 namespace = namespace[len(TEMPLATE_PREFIX) :] |
144 return namespace | 145 return namespace |
145 | 146 |
146 def get_template_ns(self, namespace: str) -> str: | 147 def get_template_ns(self, namespace: str) -> str: |
147 """Returns node used for data template (i.e. schema)""" | 148 """Returns node used for data template (i.e. schema)""" |
148 app_ns = self.get_application_ns(namespace) | 149 app_ns = self.get_application_ns(namespace) |
175 """ | 176 """ |
176 app_ns = self.get_application_ns(nodeIdentifier) | 177 app_ns = self.get_application_ns(nodeIdentifier) |
177 node_id = f"{TEMPLATE_PREFIX}{app_ns}" | 178 node_id = f"{TEMPLATE_PREFIX}{app_ns}" |
178 items_data = await self._p.get_items(client, service, node_id, max_items=1) | 179 items_data = await self._p.get_items(client, service, node_id, max_items=1) |
179 try: | 180 try: |
180 schema = next(items_data[0][0].elements(data_form.NS_X_DATA, 'x')) | 181 schema = next(items_data[0][0].elements(data_form.NS_X_DATA, "x")) |
181 except IndexError: | 182 except IndexError: |
182 schema = None | 183 schema = None |
183 except StopIteration: | 184 except StopIteration: |
184 log.warning( | 185 log.warning( |
185 f"No schema found in item of {service!r} at node {nodeIdentifier!r}: " | 186 f"No schema found in item of {service!r} at node {nodeIdentifier!r}: " |
186 f"\n{items_data[0][0].toXml()}" | 187 f"\n{items_data[0][0].toXml()}" |
187 ) | 188 ) |
188 schema = None | 189 schema = None |
189 return schema | 190 return schema |
190 | 191 |
191 async def get_schema_form(self, client, service, nodeIdentifier, schema=None, | 192 async def get_schema_form( |
192 form_type="form", copy_form=True): | 193 self, |
194 client, | |
195 service, | |
196 nodeIdentifier, | |
197 schema=None, | |
198 form_type="form", | |
199 copy_form=True, | |
200 ): | |
193 """Get data form from node's schema | 201 """Get data form from node's schema |
194 | 202 |
195 @param service(None, jid.JID): PubSub service | 203 @param service(None, jid.JID): PubSub service |
196 @param nodeIdentifier(unicode): node | 204 @param nodeIdentifier(unicode): node |
197 @param schema(domish.Element, data_form.Form, None): node schema | 205 @param schema(domish.Element, data_form.Form, None): node schema |
217 if copy_form: | 225 if copy_form: |
218 # XXX: we don't use deepcopy as it will do an infinite loop if a | 226 # XXX: we don't use deepcopy as it will do an infinite loop if a |
219 # domish.Element is present in the form fields (happens for | 227 # domish.Element is present in the form fields (happens for |
220 # XEP-0315 data forms XML Element) | 228 # XEP-0315 data forms XML Element) |
221 schema = data_form.Form( | 229 schema = data_form.Form( |
222 formType = schema.formType, | 230 formType=schema.formType, |
223 title = schema.title, | 231 title=schema.title, |
224 instructions = schema.instructions[:], | 232 instructions=schema.instructions[:], |
225 formNamespace = schema.formNamespace, | 233 formNamespace=schema.formNamespace, |
226 fields = schema.fieldList, | 234 fields=schema.fieldList, |
227 ) | 235 ) |
228 return schema | 236 return schema |
229 | 237 |
230 try: | 238 try: |
231 form = data_form.Form.fromElement(schema) | 239 form = data_form.Form.fromElement(schema) |
237 def schema_2_xmlui(self, schema_elt): | 245 def schema_2_xmlui(self, schema_elt): |
238 form = data_form.Form.fromElement(schema_elt) | 246 form = data_form.Form.fromElement(schema_elt) |
239 xmlui = xml_tools.data_form_2_xmlui(form, "") | 247 xmlui = xml_tools.data_form_2_xmlui(form, "") |
240 return xmlui | 248 return xmlui |
241 | 249 |
242 def _get_ui_schema(self, service, nodeIdentifier, default_node=None, | 250 def _get_ui_schema( |
243 profile_key=C.PROF_KEY_NONE): | 251 self, service, nodeIdentifier, default_node=None, profile_key=C.PROF_KEY_NONE |
252 ): | |
244 if not nodeIdentifier: | 253 if not nodeIdentifier: |
245 if not default_node: | 254 if not default_node: |
246 raise ValueError(_("nodeIndentifier needs to be set")) | 255 raise ValueError(_("nodeIndentifier needs to be set")) |
247 nodeIdentifier = default_node | 256 nodeIdentifier = default_node |
248 client = self.host.get_client(profile_key) | 257 client = self.host.get_client(profile_key) |
288 d = defer.ensureDeferred(self.get_schema_dict(client, service, nodeIdentifier)) | 297 d = defer.ensureDeferred(self.get_schema_dict(client, service, nodeIdentifier)) |
289 d.addCallback(data_format.serialise) | 298 d.addCallback(data_format.serialise) |
290 return d | 299 return d |
291 | 300 |
292 async def get_schema_dict( | 301 async def get_schema_dict( |
293 self, | 302 self, client: SatXMPPEntity, service: Optional[jid.JID], nodeIdentifier: str |
294 client: SatXMPPEntity, | 303 ) -> dict: |
295 service: Optional[jid.JID], | |
296 nodeIdentifier: str) -> dict: | |
297 """Retrieve a node schema and format it a simple dictionary | 304 """Retrieve a node schema and format it a simple dictionary |
298 | 305 |
299 The dictionary is made so it can be easily serialisable | 306 The dictionary is made so it can be easily serialisable |
300 """ | 307 """ |
301 schema_form = await self.get_schema_form(client, service, nodeIdentifier) | 308 schema_form = await self.get_schema_form(client, service, nodeIdentifier) |
302 return xml_tools.data_form_2_data_dict(schema_form) | 309 return xml_tools.data_form_2_data_dict(schema_form) |
303 | 310 |
304 def _get_data_form_items(self, form_ns="", service="", node="", schema="", max_items=10, | 311 def _get_data_form_items( |
305 item_ids=None, sub_id=None, extra="", | 312 self, |
306 profile_key=C.PROF_KEY_NONE): | 313 form_ns="", |
314 service="", | |
315 node="", | |
316 schema="", | |
317 max_items=10, | |
318 item_ids=None, | |
319 sub_id=None, | |
320 extra="", | |
321 profile_key=C.PROF_KEY_NONE, | |
322 ): | |
307 client = self.host.get_client(profile_key) | 323 client = self.host.get_client(profile_key) |
308 service = jid.JID(service) if service else None | 324 service = jid.JID(service) if service else None |
309 if not node: | 325 if not node: |
310 raise exceptions.DataError(_("empty node is not allowed")) | 326 raise exceptions.DataError(_("empty node is not allowed")) |
311 if schema: | 327 if schema: |
329 ) | 345 ) |
330 ) | 346 ) |
331 d.addCallback(self._p.trans_items_data) | 347 d.addCallback(self._p.trans_items_data) |
332 return d | 348 return d |
333 | 349 |
334 async def get_data_form_items(self, client, service, nodeIdentifier, schema=None, | 350 async def get_data_form_items( |
335 max_items=None, item_ids=None, sub_id=None, rsm_request=None, | 351 self, |
336 extra=None, default_node=None, form_ns=None, filters=None): | 352 client, |
353 service, | |
354 nodeIdentifier, | |
355 schema=None, | |
356 max_items=None, | |
357 item_ids=None, | |
358 sub_id=None, | |
359 rsm_request=None, | |
360 extra=None, | |
361 default_node=None, | |
362 form_ns=None, | |
363 filters=None, | |
364 ): | |
337 """Get items known as being data forms, and convert them to XMLUI | 365 """Get items known as being data forms, and convert them to XMLUI |
338 | 366 |
339 @param schema(domish.Element, data_form.Form, None): schema of the node if known | 367 @param schema(domish.Element, data_form.Form, None): schema of the node if known |
340 if None, it will be retrieved from node | 368 if None, it will be retrieved from node |
341 @param default_node(unicode): node to use if nodeIdentifier is None or empty | 369 @param default_node(unicode): node to use if nodeIdentifier is None or empty |
384 ("label", "id"), | 412 ("label", "id"), |
385 ("text", item_elt["id"], "id"), | 413 ("text", item_elt["id"], "id"), |
386 ("label", "publisher"), | 414 ("label", "publisher"), |
387 ] | 415 ] |
388 try: | 416 try: |
389 publisher = jid.JID(item_elt['publisher']) | 417 publisher = jid.JID(item_elt["publisher"]) |
390 except (KeyError, jid.InvalidFormat): | 418 except (KeyError, jid.InvalidFormat): |
391 pass | 419 pass |
392 else: | 420 else: |
393 prepend.append(("jid", publisher, "publisher")) | 421 prepend.append(("jid", publisher, "publisher")) |
394 xmlui = xml_tools.data_form_result_2_xmlui( | 422 xmlui = xml_tools.data_form_result_2_xmlui( |
402 ) | 430 ) |
403 items_xmlui.append(xmlui) | 431 items_xmlui.append(xmlui) |
404 break | 432 break |
405 return (items_xmlui, metadata) | 433 return (items_xmlui, metadata) |
406 | 434 |
407 def _send_data_form_item(self, service, nodeIdentifier, values, schema=None, | 435 def _send_data_form_item( |
408 item_id=None, extra=None, profile_key=C.PROF_KEY_NONE): | 436 self, |
437 service, | |
438 nodeIdentifier, | |
439 values, | |
440 schema=None, | |
441 item_id=None, | |
442 extra=None, | |
443 profile_key=C.PROF_KEY_NONE, | |
444 ): | |
409 client = self.host.get_client(profile_key) | 445 client = self.host.get_client(profile_key) |
410 service = None if not service else jid.JID(service) | 446 service = None if not service else jid.JID(service) |
411 if schema: | 447 if schema: |
412 schema = generic.parseXml(schema.encode("utf-8")) | 448 schema = generic.parseXml(schema.encode("utf-8")) |
413 else: | 449 else: |
426 ) | 462 ) |
427 d.addCallback(lambda ret: ret or "") | 463 d.addCallback(lambda ret: ret or "") |
428 return d | 464 return d |
429 | 465 |
430 async def send_data_form_item( | 466 async def send_data_form_item( |
431 self, client, service, nodeIdentifier, values, schema=None, item_id=None, | 467 self, |
432 extra=None, deserialise=False): | 468 client, |
469 service, | |
470 nodeIdentifier, | |
471 values, | |
472 schema=None, | |
473 item_id=None, | |
474 extra=None, | |
475 deserialise=False, | |
476 ): | |
433 """Publish an item as a dataform when we know that there is a schema | 477 """Publish an item as a dataform when we know that there is a schema |
434 | 478 |
435 @param values(dict[key(unicode), [iterable[object], object]]): values set for the | 479 @param values(dict[key(unicode), [iterable[object], object]]): values set for the |
436 form. If not iterable, will be put in a list. | 480 form. If not iterable, will be put in a list. |
437 @param schema(domish.Element, data_form.Form, None): data schema | 481 @param schema(domish.Element, data_form.Form, None): data schema |
455 except KeyError: | 499 except KeyError: |
456 log.warning( | 500 log.warning( |
457 _("field {name} doesn't exist, ignoring it").format(name=name) | 501 _("field {name} doesn't exist, ignoring it").format(name=name) |
458 ) | 502 ) |
459 continue | 503 continue |
460 if isinstance(values_list, str) or not isinstance( | 504 if isinstance(values_list, str) or not isinstance(values_list, Iterable): |
461 values_list, Iterable | |
462 ): | |
463 values_list = [values_list] | 505 values_list = [values_list] |
464 if deserialise: | 506 if deserialise: |
465 if field.fieldType == "boolean": | 507 if field.fieldType == "boolean": |
466 values_list = [C.bool(v) for v in values_list] | 508 values_list = [C.bool(v) for v in values_list] |
467 elif field.fieldType == "text-multi": | 509 elif field.fieldType == "text-multi": |
468 # for text-multi, lines must be put on separate values | 510 # for text-multi, lines must be put on separate values |
469 values_list = list( | 511 values_list = list( |
470 itertools.chain(*[v.splitlines() for v in values_list]) | 512 itertools.chain(*[v.splitlines() for v in values_list]) |
471 ) | 513 ) |
472 elif xml_tools.is_xhtml_field(field): | 514 elif xml_tools.is_xhtml_field(field): |
473 values_list = [generic.parseXml(v.encode("utf-8")) | 515 values_list = [ |
474 for v in values_list] | 516 generic.parseXml(v.encode("utf-8")) for v in values_list |
517 ] | |
475 elif "jid" in (field.fieldType or ""): | 518 elif "jid" in (field.fieldType or ""): |
476 values_list = [jid.JID(v) for v in values_list] | 519 values_list = [jid.JID(v) for v in values_list] |
477 if "list" in (field.fieldType or ""): | 520 if "list" in (field.fieldType or ""): |
478 # for lists, we check that given values are allowed in form | 521 # for lists, we check that given values are allowed in form |
479 allowed_values = [o.value for o in field.options] | 522 allowed_values = [o.value for o in field.options] |
480 values_list = [v for v in values_list if v in allowed_values] | 523 values_list = [v for v in values_list if v in allowed_values] |
481 if not values_list: | 524 if not values_list: |
482 # if values don't map to allowed values, we use default ones | 525 # if values don't map to allowed values, we use default ones |
483 values_list = field.values | 526 values_list = field.values |
484 elif field.ext_type == 'xml': | 527 elif field.ext_type == "xml": |
485 # FIXME: XML elements are not handled correctly, we need to know if we | 528 # FIXME: XML elements are not handled correctly, we need to know if we |
486 # have actual XML/XHTML, or text to escape | 529 # have actual XML/XHTML, or text to escape |
487 for idx, value in enumerate(values_list[:]): | 530 for idx, value in enumerate(values_list[:]): |
488 if isinstance(value, domish.Element): | 531 if isinstance(value, domish.Element): |
489 if (field.value and (value.name != field.value.name | 532 if field.value and ( |
490 or value.uri != field.value.uri)): | 533 value.name != field.value.name or value.uri != field.value.uri |
534 ): | |
491 # the element is not the one expected in form, so we create the right element | 535 # the element is not the one expected in form, so we create the right element |
492 # to wrap the current value | 536 # to wrap the current value |
493 wrapper_elt = domish.Element((field.value.uri, field.value.name)) | 537 wrapper_elt = domish.Element( |
538 (field.value.uri, field.value.name) | |
539 ) | |
494 wrapper_elt.addChild(value) | 540 wrapper_elt.addChild(value) |
495 values_list[idx] = wrapper_elt | 541 values_list[idx] = wrapper_elt |
496 else: | 542 else: |
497 # we have to convert the value to a domish.Element | 543 # we have to convert the value to a domish.Element |
498 if field.value and field.value.uri == C.NS_XHTML: | 544 if field.value and field.value.uri == C.NS_XHTML: |
499 div_elt = domish.Element((C.NS_XHTML, 'div')) | 545 div_elt = domish.Element((C.NS_XHTML, "div")) |
500 div_elt.addContent(str(value)) | 546 div_elt.addContent(str(value)) |
501 values_list[idx] = div_elt | 547 values_list[idx] = div_elt |
502 else: | 548 else: |
503 # only XHTML fields are handled for now | 549 # only XHTML fields are handled for now |
504 raise NotImplementedError | 550 raise NotImplementedError |
568 sub_id = None | 614 sub_id = None |
569 extra = self._p.parse_extra(extra) | 615 extra = self._p.parse_extra(extra) |
570 | 616 |
571 return client, service, node, max_items, extra, sub_id | 617 return client, service, node, max_items, extra, sub_id |
572 | 618 |
573 def _get(self, service="", node="", max_items=10, item_ids=None, sub_id=None, | 619 def _get( |
574 extra="", default_node=None, form_ns=None, filters=None, | 620 self, |
575 profile_key=C.PROF_KEY_NONE): | 621 service="", |
622 node="", | |
623 max_items=10, | |
624 item_ids=None, | |
625 sub_id=None, | |
626 extra="", | |
627 default_node=None, | |
628 form_ns=None, | |
629 filters=None, | |
630 profile_key=C.PROF_KEY_NONE, | |
631 ): | |
576 """bridge method to retrieve data from node with schema | 632 """bridge method to retrieve data from node with schema |
577 | 633 |
578 this method is a helper so dependant plugins can use it directly | 634 this method is a helper so dependant plugins can use it directly |
579 when adding *Get methods | 635 when adding *Get methods |
580 extra can have the key "labels_as_list" which is a hack to convert | 636 extra can have the key "labels_as_list" which is a hack to convert |
645 client, service, node, item_ids=[item_id] | 701 client, service, node, item_ids=[item_id] |
646 ) | 702 ) |
647 item_elt = items_data[0][0] | 703 item_elt = items_data[0][0] |
648 except Exception as e: | 704 except Exception as e: |
649 log.warning( | 705 log.warning( |
650 _("Can't get previous item, update ignored: {reason}").format( | 706 _("Can't get previous item, update ignored: {reason}").format(reason=e) |
651 reason=e | |
652 ) | |
653 ) | 707 ) |
654 else: | 708 else: |
655 # and parse it | 709 # and parse it |
656 form = data_form.findForm(item_elt, form_ns) | 710 form = data_form.findForm(item_elt, form_ns) |
657 if form is None: | 711 if form is None: |
661 else: | 715 else: |
662 for name, field in form.fields.items(): | 716 for name, field in form.fields.items(): |
663 if name not in values: | 717 if name not in values: |
664 values[name] = "\n".join(str(v) for v in field.values) | 718 values[name] = "\n".join(str(v) for v in field.values) |
665 | 719 |
666 def _set(self, service, node, values, schema=None, item_id=None, extra=None, | 720 def _set( |
667 default_node=None, form_ns=None, fill_author=True, | 721 self, |
668 profile_key=C.PROF_KEY_NONE): | 722 service, |
723 node, | |
724 values, | |
725 schema=None, | |
726 item_id=None, | |
727 extra=None, | |
728 default_node=None, | |
729 form_ns=None, | |
730 fill_author=True, | |
731 profile_key=C.PROF_KEY_NONE, | |
732 ): | |
669 """bridge method to set item in node with schema | 733 """bridge method to set item in node with schema |
670 | 734 |
671 this method is a helper so dependant plugins can use it directly | 735 this method is a helper so dependant plugins can use it directly |
672 when adding *Set methods | 736 when adding *Set methods |
673 """ | 737 """ |
674 client, service, node, schema, item_id, extra = self.prepare_bridge_set( | 738 client, service, node, schema, item_id, extra = self.prepare_bridge_set( |
675 service, node, schema, item_id, extra | 739 service, node, schema, item_id, extra |
676 ) | 740 ) |
677 d = defer.ensureDeferred(self.set( | 741 d = defer.ensureDeferred( |
678 client, | 742 self.set( |
679 service, | 743 client, |
680 node, | 744 service, |
681 values, | 745 node, |
682 schema, | 746 values, |
683 item_id, | 747 schema, |
684 extra, | 748 item_id, |
685 deserialise=True, | 749 extra, |
686 form_ns=form_ns, | 750 deserialise=True, |
687 default_node=default_node, | 751 form_ns=form_ns, |
688 fill_author=fill_author, | 752 default_node=default_node, |
689 )) | 753 fill_author=fill_author, |
754 ) | |
755 ) | |
690 d.addCallback(lambda ret: ret or "") | 756 d.addCallback(lambda ret: ret or "") |
691 return d | 757 return d |
692 | 758 |
693 async def set( | 759 async def set( |
694 self, client, service, node, values, schema, item_id, extra, deserialise, | 760 self, |
695 form_ns, default_node=None, fill_author=True): | 761 client, |
762 service, | |
763 node, | |
764 values, | |
765 schema, | |
766 item_id, | |
767 extra, | |
768 deserialise, | |
769 form_ns, | |
770 default_node=None, | |
771 fill_author=True, | |
772 ): | |
696 """Set an item in a node with a schema | 773 """Set an item in a node with a schema |
697 | 774 |
698 This method can be used directly by *Set methods added by dependant plugin | 775 This method can be used directly by *Set methods added by dependant plugin |
699 @param values(dict[key(unicode), [iterable[object]|object]]): values of the items | 776 @param values(dict[key(unicode), [iterable[object]|object]]): values of the items |
700 if value is not iterable, it will be put in a list | 777 if value is not iterable, it will be put in a list |
723 elif extra.get("update", False): | 800 elif extra.get("update", False): |
724 if item_id is None: | 801 if item_id is None: |
725 raise exceptions.DataError( | 802 raise exceptions.DataError( |
726 _('if extra["update"] is set, item_id must be set too') | 803 _('if extra["update"] is set, item_id must be set too') |
727 ) | 804 ) |
728 await self.copy_missing_values(client, service, node, item_id, form_ns, values) | 805 await self.copy_missing_values( |
806 client, service, node, item_id, form_ns, values | |
807 ) | |
729 | 808 |
730 values["updated"] = now | 809 values["updated"] = now |
731 if fill_author: | 810 if fill_author: |
732 if not values.get("author"): | 811 if not values.get("author"): |
733 id_data = await self._i.get_identity(client, None, ["nicknames"]) | 812 id_data = await self._i.get_identity(client, None, ["nicknames"]) |
734 values["author"] = id_data['nicknames'][0] | 813 values["author"] = id_data["nicknames"][0] |
735 if not values.get("author_jid"): | 814 if not values.get("author_jid"): |
736 values["author_jid"] = client.jid.full() | 815 values["author_jid"] = client.jid.full() |
737 item_id = await self.send_data_form_item( | 816 item_id = await self.send_data_form_item( |
738 client, service, node, values, schema, item_id, extra, deserialise | 817 client, service, node, values, schema, item_id, extra, deserialise |
739 ) | 818 ) |