glpi-python-client 0.6.4__py3-none-any.whl → 0.6.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -133,7 +133,7 @@ from glpi_python_client.rsql import (
133
133
  date_window,
134
134
  )
135
135
 
136
- __version__ = "0.6.4"
136
+ __version__ = "0.6.5"
137
137
 
138
138
  __all__ = [
139
139
  "AsyncGlpiClient",
@@ -179,6 +179,30 @@ def _plain_text_html(text: str) -> str:
179
179
  return "<p>" + "<br>".join(lines) + "</p>"
180
180
 
181
181
 
182
+ #: What a browser strips from the front of a URL, and drops from inside one.
183
+ _URL_LEADING = "".join(chr(c) for c in range(0x21))
184
+ _URL_INNER = str.maketrans("", "", "\t\r\n")
185
+
186
+ #: The elements inside which the reader writes no image, so offers none.
187
+ _UNFORMATTED = ("pre", "code", "kbd", "samp")
188
+
189
+
190
+ def _is_data_uri(src: str) -> bool:
191
+ """Whether a browser reads ``src`` as a ``data:`` URI.
192
+
193
+ The scheme is case-insensitive, leading C0 controls and spaces are
194
+ stripped, and tabs and line breaks are dropped wherever they are.
195
+ """
196
+
197
+ return src.lstrip(_URL_LEADING).translate(_URL_INNER).casefold().startswith("data:")
198
+
199
+
200
+ def _image_id(el: Tag) -> str | None:
201
+ """The trimmed ``id`` of an element, or ``None`` when it has none."""
202
+
203
+ return str(el.get("id") or "").strip() or None
204
+
205
+
182
206
  def _soup(html: str) -> BeautifulSoup:
183
207
  """Parse ``html`` and drop what a browser does not display."""
184
208
 
@@ -402,6 +426,12 @@ class Link:
402
426
  enclosing_href : str or None
403
427
  For an ``<img>`` inside a link, that link's ``href``; ``None``
404
428
  otherwise. The image is read before the link around it.
429
+ image_id : str or None
430
+ For an ``<img>``, the value of its ``id`` attribute, trimmed, or
431
+ ``None`` when it has none or the element is a link. GLPI sets it to
432
+ the tag of the Document it created for an image it has not rewritten
433
+ into a document link, and that Document carries the same value as its
434
+ tag, so a caller can find it.
405
435
  """
406
436
 
407
437
  href: str
@@ -410,6 +440,7 @@ class Link:
410
440
  image: bool = False
411
441
  document_id: int | None = None
412
442
  enclosing_href: str | None = None
443
+ image_id: str | None = None
413
444
 
414
445
 
415
446
  #: A ``rewrite_link`` callback: called with each link and image a read meets,
@@ -664,6 +695,7 @@ class _Converter(MarkdownConverter):
664
695
  enclosing_href=(
665
696
  None if around is None else str(around.get("href") or "")
666
697
  ),
698
+ image_id=_image_id(el),
667
699
  )
668
700
  )
669
701
  if isinstance(answer, str):
@@ -1021,6 +1053,33 @@ class GlpiContentConverter:
1021
1053
  document_id = int(values[0])
1022
1054
  return document_id if document_id > 0 else None
1023
1055
 
1056
+ @staticmethod
1057
+ def data_image_ids(html: str) -> tuple[str, ...]:
1058
+ """Return the ``id`` of every image a body keeps as a ``data:`` URI.
1059
+
1060
+ Each ``<img>`` whose ``src`` a browser reads as a ``data:`` URI and
1061
+ that has a non-empty ``id`` contributes it, in document order and
1062
+ without repeats. The base64 is not decoded. An image inside ``<pre>``,
1063
+ ``<code>``, ``<kbd>`` or ``<samp>`` is left out: the reader shows it
1064
+ as no image, so it never offers it to ``rewrite_link`` either.
1065
+
1066
+ GLPI sets a tag on the Documents it creates from an upload or an
1067
+ editor paste, images or not. For an image it has not rewritten into a
1068
+ document link, that tag is the ``id`` of the ``<img>``
1069
+ (:attr:`Link.image_id`, :attr:`GetDocument.tag`). A caller calls this
1070
+ first, to decide whether it needs to read any Document at all.
1071
+ """
1072
+
1073
+ if "<" not in html:
1074
+ return ()
1075
+ ids = (
1076
+ _image_id(img)
1077
+ for img in _soup(html).find_all("img")
1078
+ if _is_data_uri(str(img.get("src") or ""))
1079
+ and img.find_parent(_UNFORMATTED) is None
1080
+ )
1081
+ return tuple(dict.fromkeys(i for i in ids if i is not None))
1082
+
1024
1083
  @staticmethod
1025
1084
  def document_image(
1026
1085
  document_id: int,
@@ -61,6 +61,20 @@ class GetDocument(GlpiModel):
61
61
  mime: str | None = None
62
62
  sha1sum: str | None = None
63
63
 
64
+ @property
65
+ def tag(self) -> str | None:
66
+ """The tag GLPI gave the Document, or ``None``.
67
+
68
+ GLPI sets a tag on the Documents it creates from an upload or an
69
+ editor paste, images or not. For an image it has not rewritten into
70
+ a document link, it is the ``id`` of the ``<img>`` left in the body
71
+ (see ``GlpiContentConverter.data_image_ids``). Read from
72
+ ``extra_payload``; ``None`` when absent, empty or not a string.
73
+ """
74
+
75
+ value = self.extra_payload.get("tag")
76
+ return value if isinstance(value, str) and value else None
77
+
64
78
 
65
79
  class PostDocument(GlpiModel):
66
80
  """Request body for ``POST /Management/Document``.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: glpi-python-client
3
- Version: 0.6.4
3
+ Version: 0.6.5
4
4
  Summary: A typed Python client for GLPI ITSM APIs.
5
5
  Project-URL: Homepage, https://github.com/baraline/glpi_python_client
6
6
  Project-URL: Documentation, https://glpi-python-client.readthedocs.io/en/latest/
@@ -1,4 +1,4 @@
1
- glpi_python_client/__init__.py,sha256=IjuloET5lqy2KL5c3sgXRYxppjxcVAv7mZUK-DERRNA,5630
1
+ glpi_python_client/__init__.py,sha256=goD0YVtmpFX2_3vXrg-qOCLUgtCYwEj-wFJeP0-UfLk,5630
2
2
  glpi_python_client/_errors.py,sha256=Bc9UvlxulbdcGvzZIKXnJCTL78sW78R6EFg50P-1dpY,7151
3
3
  glpi_python_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  glpi_python_client/rsql.py,sha256=3u3lH3_-F1bvHbVzxALXTDPoHyoMATP1f4QPTar_sEU,8974
@@ -95,7 +95,7 @@ glpi_python_client/_sync/clients/custom/__init__.py,sha256=sxfGlHVulucP7bacWhUMi
95
95
  glpi_python_client/_sync/clients/custom/_statistics.py,sha256=xrD5C99ieROJx60iUomBHacQ_49HpgQL4XjnfREUQvs,38616
96
96
  glpi_python_client/_sync/clients/custom/_ticket_context.py,sha256=LDUvKNr8VpZonF5bDo8QYYwMEsWmayVU28Ra3GonNmE,3004
97
97
  glpi_python_client/content/__init__.py,sha256=TaZPo8WSvVj4U-hSnxhUcqU8JnCKNZYpfQf4pH45aN4,424
98
- glpi_python_client/content/conversion.py,sha256=bglYY5IgoTv2SdS0Gton7WgsyzwwQ5GdU_2dLloL2l0,42860
98
+ glpi_python_client/content/conversion.py,sha256=0dxQW0ojSo1KC8zcTFa476KQa1i3sLWzQ8gTEEVslys,45341
99
99
  glpi_python_client/models/__init__.py,sha256=CLvk-1A-88114X7HaS9P6KyfovQjCPRX08J56rJaGls,5454
100
100
  glpi_python_client/models/_base.py,sha256=pYg69KHuR5TDED5utr4Knr7HncBnLTxXHJZYf6vEUKA,12183
101
101
  glpi_python_client/models/api_schema/__init__.py,sha256=yTxUYih1Xy7NUogzD7UKp2b8HyE4wdeC_FCKS6ZRVVM,1176
@@ -127,7 +127,7 @@ glpi_python_client/models/api_schema/knowledgebase/_revision.py,sha256=KuTb_F85m
127
127
  glpi_python_client/models/api_schema/management/__init__.py,sha256=z5ntUAXiXXhZCEIsTVaTgJdXbg0ZW24TZctarfAqjr0,987
128
128
  glpi_python_client/models/api_schema/management/_contract.py,sha256=m6g__AKWvDQ09xKyUe8RhRS8xE1d1pOBjqOOG-8u6Ck,14045
129
129
  glpi_python_client/models/api_schema/management/_contract_cost.py,sha256=Bp2B3bA5u8CE_oBYY70QIUot8jFLCrX_icXZ4kpYqSE,4966
130
- glpi_python_client/models/api_schema/management/_document.py,sha256=RzKF-rNlz8CsAMa2BAaiqHNEE3-m1mci7vtdARls4uw,4607
130
+ glpi_python_client/models/api_schema/management/_document.py,sha256=kiEkTnOydGHjOyAwTbo020Kei4ffkBDj-9m775gLyfw,5200
131
131
  glpi_python_client/models/api_schema/management/_document_item.py,sha256=Bu9c9YdhSs5hGZ8dRfRxqS4g7VZ1A3EJtNbDLulKpWA,3113
132
132
  glpi_python_client/models/api_schema/plugins/__init__.py,sha256=GA0pr0PeoPOqwjUilrBujh0OqjUfZhzPWgIlgDJcZog,544
133
133
  glpi_python_client/models/api_schema/plugins/_fields.py,sha256=Se-12ZDqk_-on_37YF_hRD5S0e0H6zyha3SXI9xb_9Y,6576
@@ -136,7 +136,7 @@ glpi_python_client/models/custom_schema/_ticket_context.py,sha256=jnlfgho-aDbmA9
136
136
  glpi_python_client/testing/__init__.py,sha256=iLtUyJjyv3fEhy3PN25NxNTbtqDl3SoHLbNium0Z80U,568
137
137
  glpi_python_client/testing/fixtures.py,sha256=HKt6MXHgcmLn4L98pSwp-oGlc9C--_vrABNe5G_iBeg,1286
138
138
  glpi_python_client/testing/utils.py,sha256=JyO3w2d-0kYdtC1hEZ2Tu_YjnCz8nUQ_Ck05-1EhsX4,4404
139
- glpi_python_client-0.6.4.dist-info/METADATA,sha256=EFNn15U6Eqjua7bVLPM22W9N48EEMza3gq3f0--MsJs,8234
140
- glpi_python_client-0.6.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
141
- glpi_python_client-0.6.4.dist-info/licenses/LICENSE,sha256=nL8re-TIKkxCoL7OkSBja6EB4NsQR1HoOJgxv_MEhtc,1088
142
- glpi_python_client-0.6.4.dist-info/RECORD,,
139
+ glpi_python_client-0.6.5.dist-info/METADATA,sha256=xKVBulZ8pipAYJLr8FuRcAM1M21XFiM9PGGq3PNMeZE,8234
140
+ glpi_python_client-0.6.5.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
141
+ glpi_python_client-0.6.5.dist-info/licenses/LICENSE,sha256=nL8re-TIKkxCoL7OkSBja6EB4NsQR1HoOJgxv_MEhtc,1088
142
+ glpi_python_client-0.6.5.dist-info/RECORD,,