glpi-python-client 0.6.3__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.
- glpi_python_client/__init__.py +5 -1
- glpi_python_client/_async/clients/api/assistance/timeline/_document.py +185 -0
- glpi_python_client/_sync/clients/api/assistance/timeline/_document.py +185 -0
- glpi_python_client/content/conversion.py +59 -0
- glpi_python_client/models/__init__.py +4 -0
- glpi_python_client/models/api_schema/enums.py +5 -0
- glpi_python_client/models/api_schema/management/__init__.py +6 -0
- glpi_python_client/models/api_schema/management/_document.py +14 -0
- glpi_python_client/models/api_schema/management/_document_item.py +88 -0
- {glpi_python_client-0.6.3.dist-info → glpi_python_client-0.6.5.dist-info}/METADATA +1 -1
- {glpi_python_client-0.6.3.dist-info → glpi_python_client-0.6.5.dist-info}/RECORD +13 -12
- {glpi_python_client-0.6.3.dist-info → glpi_python_client-0.6.5.dist-info}/WHEEL +0 -0
- {glpi_python_client-0.6.3.dist-info → glpi_python_client-0.6.5.dist-info}/licenses/LICENSE +0 -0
glpi_python_client/__init__.py
CHANGED
|
@@ -50,12 +50,14 @@ from glpi_python_client.models import (
|
|
|
50
50
|
DeleteTicketTask,
|
|
51
51
|
DeleteTimelineDocument,
|
|
52
52
|
DeleteUser,
|
|
53
|
+
FollowupDocuments,
|
|
53
54
|
GetComputer,
|
|
54
55
|
GetContract,
|
|
55
56
|
GetContractCost,
|
|
56
57
|
GetContractItem,
|
|
57
58
|
GetContractType,
|
|
58
59
|
GetDocument,
|
|
60
|
+
GetDocumentItem,
|
|
59
61
|
GetEntity,
|
|
60
62
|
GetFollowup,
|
|
61
63
|
GetKBArticle,
|
|
@@ -131,7 +133,7 @@ from glpi_python_client.rsql import (
|
|
|
131
133
|
date_window,
|
|
132
134
|
)
|
|
133
135
|
|
|
134
|
-
__version__ = "0.6.
|
|
136
|
+
__version__ = "0.6.5"
|
|
135
137
|
|
|
136
138
|
__all__ = [
|
|
137
139
|
"AsyncGlpiClient",
|
|
@@ -153,12 +155,14 @@ __all__ = [
|
|
|
153
155
|
"DeleteTicketTask",
|
|
154
156
|
"DeleteTimelineDocument",
|
|
155
157
|
"DeleteUser",
|
|
158
|
+
"FollowupDocuments",
|
|
156
159
|
"GetComputer",
|
|
157
160
|
"GetContract",
|
|
158
161
|
"GetContractCost",
|
|
159
162
|
"GetContractItem",
|
|
160
163
|
"GetContractType",
|
|
161
164
|
"GetDocument",
|
|
165
|
+
"GetDocumentItem",
|
|
162
166
|
"GetEntity",
|
|
163
167
|
"GetFollowup",
|
|
164
168
|
"GetKBArticle",
|
|
@@ -17,18 +17,36 @@ as :class:`GetDocument`.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
from glpi_python_client._async._concurrency import gather
|
|
20
23
|
from glpi_python_client._async.clients.commons._constants import (
|
|
21
24
|
TICKET_ENDPOINT,
|
|
22
25
|
TIMELINE_DOCUMENT_SUFFIX,
|
|
23
26
|
GlpiId,
|
|
24
27
|
)
|
|
28
|
+
from glpi_python_client._async.clients.commons._payloads import model_from_payload
|
|
25
29
|
from glpi_python_client._async.clients.commons._transport import TransportMixin
|
|
30
|
+
from glpi_python_client._errors import GlpiValidationError
|
|
26
31
|
from glpi_python_client.models.api_schema.assistance.timeline._document import (
|
|
27
32
|
DeleteTimelineDocument,
|
|
28
33
|
PatchTimelineDocument,
|
|
29
34
|
PostTimelineDocument,
|
|
30
35
|
)
|
|
36
|
+
from glpi_python_client.models.api_schema.assistance.timeline._followup import (
|
|
37
|
+
GetFollowup,
|
|
38
|
+
)
|
|
31
39
|
from glpi_python_client.models.api_schema.management._document import GetDocument
|
|
40
|
+
from glpi_python_client.models.api_schema.management._document_item import (
|
|
41
|
+
FollowupDocuments,
|
|
42
|
+
GetDocumentItem,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_ITEM_DOCUMENT_RANGE = "0-999"
|
|
46
|
+
_ITEM_DOCUMENT_PAGE_SIZE = 1000
|
|
47
|
+
_ITEMTYPE_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9_]*$")
|
|
48
|
+
_V1_FEATURE_LABEL = "item document link listing"
|
|
49
|
+
_FOLLOWUP_ITEMTYPE = "ITILFollowup"
|
|
32
50
|
|
|
33
51
|
|
|
34
52
|
class TimelineDocumentMixin(TransportMixin):
|
|
@@ -62,6 +80,173 @@ class TimelineDocumentMixin(TransportMixin):
|
|
|
62
80
|
unwrap_envelope=True,
|
|
63
81
|
)
|
|
64
82
|
|
|
83
|
+
async def list_item_documents(
|
|
84
|
+
self, itemtype: str, items_id: GlpiId
|
|
85
|
+
) -> list[GetDocumentItem]:
|
|
86
|
+
"""List the document links of one item, over the legacy v1 API.
|
|
87
|
+
|
|
88
|
+
Only the followup route (``itemtype="ITILFollowup"``) has been
|
|
89
|
+
checked against a live server. Other itemtypes are sent to the same
|
|
90
|
+
v1 sub-route unchanged.
|
|
91
|
+
|
|
92
|
+
Unlike :meth:`list_ticket_timeline_documents`, this call needs the
|
|
93
|
+
legacy v1 session (``v1_base_url`` and ``v1_user_token``). Without
|
|
94
|
+
one it raises ``RuntimeError``.
|
|
95
|
+
|
|
96
|
+
It returns link rows, not documents. A caller resolves each
|
|
97
|
+
``documents_id`` with ``get_document``.
|
|
98
|
+
|
|
99
|
+
The library does not filter private followups, tasks or solutions
|
|
100
|
+
for the caller: every link the authenticated user can read is
|
|
101
|
+
returned.
|
|
102
|
+
|
|
103
|
+
GLPI's editor links an image pasted into a followup to the followup,
|
|
104
|
+
not to the ticket. A caller that wants such images therefore walks
|
|
105
|
+
the ticket's followups and lists each one.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
itemtype : str
|
|
110
|
+
GLPI itemtype owning the links, for example ``"ITILFollowup"``.
|
|
111
|
+
It must start with a letter and contain only letters, digits and
|
|
112
|
+
underscores, because it is placed in the URL path.
|
|
113
|
+
items_id : GlpiId
|
|
114
|
+
Positive numeric identifier of the item.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
list[GetDocumentItem]
|
|
119
|
+
Link rows, with naive timestamps localised to the server
|
|
120
|
+
timezone. An empty answer gives an empty list.
|
|
121
|
+
|
|
122
|
+
Raises
|
|
123
|
+
------
|
|
124
|
+
RuntimeError
|
|
125
|
+
If the client was built without a v1 session.
|
|
126
|
+
GlpiValidationError
|
|
127
|
+
If ``itemtype`` or ``items_id`` is invalid, if GLPI answers
|
|
128
|
+
something other than a list or an empty body, or if it answers a
|
|
129
|
+
full page of 1000 rows or more, which may be truncated. A
|
|
130
|
+
partial list is never returned.
|
|
131
|
+
GlpiStatusError
|
|
132
|
+
If the GLPI server returns a non-success HTTP status.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
v1 = self._require_v1_session(_V1_FEATURE_LABEL)
|
|
136
|
+
if not _ITEMTYPE_PATTERN.fullmatch(itemtype):
|
|
137
|
+
raise GlpiValidationError(
|
|
138
|
+
f"itemtype must match {_ITEMTYPE_PATTERN.pattern!r}, got {itemtype!r}"
|
|
139
|
+
)
|
|
140
|
+
if items_id <= 0:
|
|
141
|
+
raise GlpiValidationError(
|
|
142
|
+
f"items_id must be a positive integer, got {items_id!r}"
|
|
143
|
+
)
|
|
144
|
+
payload = await v1.request_json(
|
|
145
|
+
"GET",
|
|
146
|
+
f"{itemtype}/{items_id}/Document_Item",
|
|
147
|
+
params={"range": _ITEM_DOCUMENT_RANGE},
|
|
148
|
+
failure_message=(f"Failed to list document links of {itemtype} {items_id}"),
|
|
149
|
+
)
|
|
150
|
+
if isinstance(payload, dict) and not payload:
|
|
151
|
+
return []
|
|
152
|
+
if not isinstance(payload, list):
|
|
153
|
+
raise GlpiValidationError(
|
|
154
|
+
f"Unexpected document link answer for {itemtype} {items_id}: "
|
|
155
|
+
f"{type(payload).__name__}"
|
|
156
|
+
)
|
|
157
|
+
if len(payload) >= _ITEM_DOCUMENT_PAGE_SIZE:
|
|
158
|
+
raise GlpiValidationError(
|
|
159
|
+
f"{itemtype} {items_id} returned {len(payload)} document links, "
|
|
160
|
+
"a full page that may be truncated"
|
|
161
|
+
)
|
|
162
|
+
return [
|
|
163
|
+
model_from_payload(
|
|
164
|
+
GetDocumentItem, row, server_timezone=self.server_timezone
|
|
165
|
+
)
|
|
166
|
+
for row in payload
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
async def list_ticket_followup_documents(
|
|
170
|
+
self, ticket_id: GlpiId, *, include_private: bool = False
|
|
171
|
+
) -> list[FollowupDocuments]:
|
|
172
|
+
"""List the document links of each followup of one ticket.
|
|
173
|
+
|
|
174
|
+
GLPI's editor links an image pasted into a followup to the followup,
|
|
175
|
+
not to the ticket. :meth:`list_ticket_timeline_documents` therefore
|
|
176
|
+
never returns such an image, and this method does.
|
|
177
|
+
|
|
178
|
+
Each entry holds the followup and its links, so a caller reads
|
|
179
|
+
``followup.is_private`` to classify every link without a second
|
|
180
|
+
read. Only followups are walked: tasks and solutions are not.
|
|
181
|
+
|
|
182
|
+
This call needs the legacy v1 session (``v1_base_url`` and
|
|
183
|
+
``v1_user_token``). Without one it raises ``RuntimeError``. It issues
|
|
184
|
+
one v1 request per kept followup, all at once on the asynchronous
|
|
185
|
+
surface and one after the other on the synchronous one.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
ticket_id : GlpiId
|
|
190
|
+
Numeric identifier of the parent ticket.
|
|
191
|
+
include_private : bool, optional
|
|
192
|
+
When false (the default), private followups and followups whose
|
|
193
|
+
``is_private`` flag is missing are dropped and not queried, so
|
|
194
|
+
only what a requester can see is returned. A missing flag counts
|
|
195
|
+
as private. When true, every followup is kept.
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
list[FollowupDocuments]
|
|
200
|
+
One entry per kept followup, in ascending followup id, including
|
|
201
|
+
a followup with no link (an empty ``documents`` list). A link row
|
|
202
|
+
that does not name that followup as its item is dropped.
|
|
203
|
+
|
|
204
|
+
Raises
|
|
205
|
+
------
|
|
206
|
+
RuntimeError
|
|
207
|
+
If the client was built without a v1 session.
|
|
208
|
+
GlpiValidationError
|
|
209
|
+
If a kept followup has no identifier, or if reading the links of
|
|
210
|
+
a followup fails validation. A partial list is never returned.
|
|
211
|
+
GlpiStatusError
|
|
212
|
+
If the GLPI server returns a non-success HTTP status.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
self._require_v1_session(_V1_FEATURE_LABEL)
|
|
216
|
+
followups = await self.list_ticket_followups(ticket_id) # type: ignore[attr-defined]
|
|
217
|
+
kept = [
|
|
218
|
+
followup
|
|
219
|
+
for followup in followups
|
|
220
|
+
if include_private or followup.is_private is False
|
|
221
|
+
]
|
|
222
|
+
keyed: list[tuple[int, GetFollowup]] = []
|
|
223
|
+
for followup in kept:
|
|
224
|
+
if followup.id is None:
|
|
225
|
+
raise GlpiValidationError(
|
|
226
|
+
f"A followup of ticket {ticket_id} has no id, "
|
|
227
|
+
"so its document links cannot be read"
|
|
228
|
+
)
|
|
229
|
+
keyed.append((followup.id, followup))
|
|
230
|
+
keyed.sort(key=lambda pair: pair[0])
|
|
231
|
+
link_lists = await gather(
|
|
232
|
+
*(
|
|
233
|
+
self.list_item_documents(_FOLLOWUP_ITEMTYPE, followup_id)
|
|
234
|
+
for followup_id, _ in keyed
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
return [
|
|
238
|
+
FollowupDocuments(
|
|
239
|
+
followup=followup,
|
|
240
|
+
documents=[
|
|
241
|
+
row
|
|
242
|
+
for row in rows
|
|
243
|
+
if row.itemtype == _FOLLOWUP_ITEMTYPE
|
|
244
|
+
and row.items_id == followup_id
|
|
245
|
+
],
|
|
246
|
+
)
|
|
247
|
+
for (followup_id, followup), rows in zip(keyed, link_lists, strict=True)
|
|
248
|
+
]
|
|
249
|
+
|
|
65
250
|
async def get_ticket_timeline_document(
|
|
66
251
|
self, ticket_id: GlpiId, document_link_id: GlpiId
|
|
67
252
|
) -> GetDocument:
|
|
@@ -17,18 +17,36 @@ as :class:`GetDocument`.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
from glpi_python_client._sync._concurrency import gather
|
|
20
23
|
from glpi_python_client._sync.clients.commons._constants import (
|
|
21
24
|
TICKET_ENDPOINT,
|
|
22
25
|
TIMELINE_DOCUMENT_SUFFIX,
|
|
23
26
|
GlpiId,
|
|
24
27
|
)
|
|
28
|
+
from glpi_python_client._sync.clients.commons._payloads import model_from_payload
|
|
25
29
|
from glpi_python_client._sync.clients.commons._transport import TransportMixin
|
|
30
|
+
from glpi_python_client._errors import GlpiValidationError
|
|
26
31
|
from glpi_python_client.models.api_schema.assistance.timeline._document import (
|
|
27
32
|
DeleteTimelineDocument,
|
|
28
33
|
PatchTimelineDocument,
|
|
29
34
|
PostTimelineDocument,
|
|
30
35
|
)
|
|
36
|
+
from glpi_python_client.models.api_schema.assistance.timeline._followup import (
|
|
37
|
+
GetFollowup,
|
|
38
|
+
)
|
|
31
39
|
from glpi_python_client.models.api_schema.management._document import GetDocument
|
|
40
|
+
from glpi_python_client.models.api_schema.management._document_item import (
|
|
41
|
+
FollowupDocuments,
|
|
42
|
+
GetDocumentItem,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_ITEM_DOCUMENT_RANGE = "0-999"
|
|
46
|
+
_ITEM_DOCUMENT_PAGE_SIZE = 1000
|
|
47
|
+
_ITEMTYPE_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9_]*$")
|
|
48
|
+
_V1_FEATURE_LABEL = "item document link listing"
|
|
49
|
+
_FOLLOWUP_ITEMTYPE = "ITILFollowup"
|
|
32
50
|
|
|
33
51
|
|
|
34
52
|
class TimelineDocumentMixin(TransportMixin):
|
|
@@ -62,6 +80,173 @@ class TimelineDocumentMixin(TransportMixin):
|
|
|
62
80
|
unwrap_envelope=True,
|
|
63
81
|
)
|
|
64
82
|
|
|
83
|
+
def list_item_documents(
|
|
84
|
+
self, itemtype: str, items_id: GlpiId
|
|
85
|
+
) -> list[GetDocumentItem]:
|
|
86
|
+
"""List the document links of one item, over the legacy v1 API.
|
|
87
|
+
|
|
88
|
+
Only the followup route (``itemtype="ITILFollowup"``) has been
|
|
89
|
+
checked against a live server. Other itemtypes are sent to the same
|
|
90
|
+
v1 sub-route unchanged.
|
|
91
|
+
|
|
92
|
+
Unlike :meth:`list_ticket_timeline_documents`, this call needs the
|
|
93
|
+
legacy v1 session (``v1_base_url`` and ``v1_user_token``). Without
|
|
94
|
+
one it raises ``RuntimeError``.
|
|
95
|
+
|
|
96
|
+
It returns link rows, not documents. A caller resolves each
|
|
97
|
+
``documents_id`` with ``get_document``.
|
|
98
|
+
|
|
99
|
+
The library does not filter private followups, tasks or solutions
|
|
100
|
+
for the caller: every link the authenticated user can read is
|
|
101
|
+
returned.
|
|
102
|
+
|
|
103
|
+
GLPI's editor links an image pasted into a followup to the followup,
|
|
104
|
+
not to the ticket. A caller that wants such images therefore walks
|
|
105
|
+
the ticket's followups and lists each one.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
itemtype : str
|
|
110
|
+
GLPI itemtype owning the links, for example ``"ITILFollowup"``.
|
|
111
|
+
It must start with a letter and contain only letters, digits and
|
|
112
|
+
underscores, because it is placed in the URL path.
|
|
113
|
+
items_id : GlpiId
|
|
114
|
+
Positive numeric identifier of the item.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
list[GetDocumentItem]
|
|
119
|
+
Link rows, with naive timestamps localised to the server
|
|
120
|
+
timezone. An empty answer gives an empty list.
|
|
121
|
+
|
|
122
|
+
Raises
|
|
123
|
+
------
|
|
124
|
+
RuntimeError
|
|
125
|
+
If the client was built without a v1 session.
|
|
126
|
+
GlpiValidationError
|
|
127
|
+
If ``itemtype`` or ``items_id`` is invalid, if GLPI answers
|
|
128
|
+
something other than a list or an empty body, or if it answers a
|
|
129
|
+
full page of 1000 rows or more, which may be truncated. A
|
|
130
|
+
partial list is never returned.
|
|
131
|
+
GlpiStatusError
|
|
132
|
+
If the GLPI server returns a non-success HTTP status.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
v1 = self._require_v1_session(_V1_FEATURE_LABEL)
|
|
136
|
+
if not _ITEMTYPE_PATTERN.fullmatch(itemtype):
|
|
137
|
+
raise GlpiValidationError(
|
|
138
|
+
f"itemtype must match {_ITEMTYPE_PATTERN.pattern!r}, got {itemtype!r}"
|
|
139
|
+
)
|
|
140
|
+
if items_id <= 0:
|
|
141
|
+
raise GlpiValidationError(
|
|
142
|
+
f"items_id must be a positive integer, got {items_id!r}"
|
|
143
|
+
)
|
|
144
|
+
payload = v1.request_json(
|
|
145
|
+
"GET",
|
|
146
|
+
f"{itemtype}/{items_id}/Document_Item",
|
|
147
|
+
params={"range": _ITEM_DOCUMENT_RANGE},
|
|
148
|
+
failure_message=(f"Failed to list document links of {itemtype} {items_id}"),
|
|
149
|
+
)
|
|
150
|
+
if isinstance(payload, dict) and not payload:
|
|
151
|
+
return []
|
|
152
|
+
if not isinstance(payload, list):
|
|
153
|
+
raise GlpiValidationError(
|
|
154
|
+
f"Unexpected document link answer for {itemtype} {items_id}: "
|
|
155
|
+
f"{type(payload).__name__}"
|
|
156
|
+
)
|
|
157
|
+
if len(payload) >= _ITEM_DOCUMENT_PAGE_SIZE:
|
|
158
|
+
raise GlpiValidationError(
|
|
159
|
+
f"{itemtype} {items_id} returned {len(payload)} document links, "
|
|
160
|
+
"a full page that may be truncated"
|
|
161
|
+
)
|
|
162
|
+
return [
|
|
163
|
+
model_from_payload(
|
|
164
|
+
GetDocumentItem, row, server_timezone=self.server_timezone
|
|
165
|
+
)
|
|
166
|
+
for row in payload
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
def list_ticket_followup_documents(
|
|
170
|
+
self, ticket_id: GlpiId, *, include_private: bool = False
|
|
171
|
+
) -> list[FollowupDocuments]:
|
|
172
|
+
"""List the document links of each followup of one ticket.
|
|
173
|
+
|
|
174
|
+
GLPI's editor links an image pasted into a followup to the followup,
|
|
175
|
+
not to the ticket. :meth:`list_ticket_timeline_documents` therefore
|
|
176
|
+
never returns such an image, and this method does.
|
|
177
|
+
|
|
178
|
+
Each entry holds the followup and its links, so a caller reads
|
|
179
|
+
``followup.is_private`` to classify every link without a second
|
|
180
|
+
read. Only followups are walked: tasks and solutions are not.
|
|
181
|
+
|
|
182
|
+
This call needs the legacy v1 session (``v1_base_url`` and
|
|
183
|
+
``v1_user_token``). Without one it raises ``RuntimeError``. It issues
|
|
184
|
+
one v1 request per kept followup, all at once on the asynchronous
|
|
185
|
+
surface and one after the other on the synchronous one.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
ticket_id : GlpiId
|
|
190
|
+
Numeric identifier of the parent ticket.
|
|
191
|
+
include_private : bool, optional
|
|
192
|
+
When false (the default), private followups and followups whose
|
|
193
|
+
``is_private`` flag is missing are dropped and not queried, so
|
|
194
|
+
only what a requester can see is returned. A missing flag counts
|
|
195
|
+
as private. When true, every followup is kept.
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
list[FollowupDocuments]
|
|
200
|
+
One entry per kept followup, in ascending followup id, including
|
|
201
|
+
a followup with no link (an empty ``documents`` list). A link row
|
|
202
|
+
that does not name that followup as its item is dropped.
|
|
203
|
+
|
|
204
|
+
Raises
|
|
205
|
+
------
|
|
206
|
+
RuntimeError
|
|
207
|
+
If the client was built without a v1 session.
|
|
208
|
+
GlpiValidationError
|
|
209
|
+
If a kept followup has no identifier, or if reading the links of
|
|
210
|
+
a followup fails validation. A partial list is never returned.
|
|
211
|
+
GlpiStatusError
|
|
212
|
+
If the GLPI server returns a non-success HTTP status.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
self._require_v1_session(_V1_FEATURE_LABEL)
|
|
216
|
+
followups = self.list_ticket_followups(ticket_id) # type: ignore[attr-defined]
|
|
217
|
+
kept = [
|
|
218
|
+
followup
|
|
219
|
+
for followup in followups
|
|
220
|
+
if include_private or followup.is_private is False
|
|
221
|
+
]
|
|
222
|
+
keyed: list[tuple[int, GetFollowup]] = []
|
|
223
|
+
for followup in kept:
|
|
224
|
+
if followup.id is None:
|
|
225
|
+
raise GlpiValidationError(
|
|
226
|
+
f"A followup of ticket {ticket_id} has no id, "
|
|
227
|
+
"so its document links cannot be read"
|
|
228
|
+
)
|
|
229
|
+
keyed.append((followup.id, followup))
|
|
230
|
+
keyed.sort(key=lambda pair: pair[0])
|
|
231
|
+
link_lists = gather(
|
|
232
|
+
*(
|
|
233
|
+
self.list_item_documents(_FOLLOWUP_ITEMTYPE, followup_id)
|
|
234
|
+
for followup_id, _ in keyed
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
return [
|
|
238
|
+
FollowupDocuments(
|
|
239
|
+
followup=followup,
|
|
240
|
+
documents=[
|
|
241
|
+
row
|
|
242
|
+
for row in rows
|
|
243
|
+
if row.itemtype == _FOLLOWUP_ITEMTYPE
|
|
244
|
+
and row.items_id == followup_id
|
|
245
|
+
],
|
|
246
|
+
)
|
|
247
|
+
for (followup_id, followup), rows in zip(keyed, link_lists, strict=True)
|
|
248
|
+
]
|
|
249
|
+
|
|
65
250
|
def get_ticket_timeline_document(
|
|
66
251
|
self, ticket_id: GlpiId, document_link_id: GlpiId
|
|
67
252
|
) -> GetDocument:
|
|
@@ -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,
|
|
@@ -109,9 +109,11 @@ from glpi_python_client.models.api_schema.management import (
|
|
|
109
109
|
DeleteContract,
|
|
110
110
|
DeleteContractCost,
|
|
111
111
|
DeleteDocument,
|
|
112
|
+
FollowupDocuments,
|
|
112
113
|
GetContract,
|
|
113
114
|
GetContractCost,
|
|
114
115
|
GetDocument,
|
|
116
|
+
GetDocumentItem,
|
|
115
117
|
PatchContract,
|
|
116
118
|
PatchContractCost,
|
|
117
119
|
PatchDocument,
|
|
@@ -149,12 +151,14 @@ __all__ = [
|
|
|
149
151
|
"DeleteTicketTask",
|
|
150
152
|
"DeleteTimelineDocument",
|
|
151
153
|
"DeleteUser",
|
|
154
|
+
"FollowupDocuments",
|
|
152
155
|
"GetComputer",
|
|
153
156
|
"GetContract",
|
|
154
157
|
"GetContractCost",
|
|
155
158
|
"GetContractItem",
|
|
156
159
|
"GetContractType",
|
|
157
160
|
"GetDocument",
|
|
161
|
+
"GetDocumentItem",
|
|
158
162
|
"GetEntity",
|
|
159
163
|
"GetFollowup",
|
|
160
164
|
"GetKBArticle",
|
|
@@ -135,9 +135,14 @@ class GlpiTimelinePosition(GlpiEnum):
|
|
|
135
135
|
The contract advertises ``[-1, 0, 1, 2, 3, 4]`` on
|
|
136
136
|
``timeline_position``. ``LEFT`` and ``RIGHT`` follow the GLPI UI
|
|
137
137
|
conventions.
|
|
138
|
+
|
|
139
|
+
``NO_TIMELINE`` is GLPI's own name for ``-1`` and an alias of
|
|
140
|
+
``INVALID``. GLPI's editor writes it on the link of an image it embeds
|
|
141
|
+
in a text, and such a link is hidden from the ticket's Documents tab.
|
|
138
142
|
"""
|
|
139
143
|
|
|
140
144
|
INVALID = -1
|
|
145
|
+
NO_TIMELINE = -1
|
|
141
146
|
NONE = 0
|
|
142
147
|
LEFT = 1
|
|
143
148
|
RIGHT = 2
|
|
@@ -18,14 +18,20 @@ from glpi_python_client.models.api_schema.management._document import (
|
|
|
18
18
|
PatchDocument,
|
|
19
19
|
PostDocument,
|
|
20
20
|
)
|
|
21
|
+
from glpi_python_client.models.api_schema.management._document_item import (
|
|
22
|
+
FollowupDocuments,
|
|
23
|
+
GetDocumentItem,
|
|
24
|
+
)
|
|
21
25
|
|
|
22
26
|
__all__ = [
|
|
23
27
|
"DeleteContract",
|
|
24
28
|
"DeleteContractCost",
|
|
25
29
|
"DeleteDocument",
|
|
30
|
+
"FollowupDocuments",
|
|
26
31
|
"GetContract",
|
|
27
32
|
"GetContractCost",
|
|
28
33
|
"GetDocument",
|
|
34
|
+
"GetDocumentItem",
|
|
29
35
|
"PatchContract",
|
|
30
36
|
"PatchContractCost",
|
|
31
37
|
"PatchDocument",
|
|
@@ -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``.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""GLPI ``Document_Item`` link rows, as the legacy v1 API returns them.
|
|
2
|
+
|
|
3
|
+
A ``Document_Item`` row links one document to one item: a ticket, a followup,
|
|
4
|
+
a task or a solution. The v1 sub-item route ``<Itemtype>/<id>/Document_Item``
|
|
5
|
+
answers a list of these rows. They are not the ``GetTimelineDocument`` shape
|
|
6
|
+
of the v2 timeline, which carries no entity and no dates.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
from pydantic import ConfigDict
|
|
14
|
+
|
|
15
|
+
from glpi_python_client.models._base import GlpiModel
|
|
16
|
+
from glpi_python_client.models.api_schema.assistance.timeline._followup import (
|
|
17
|
+
GetFollowup,
|
|
18
|
+
)
|
|
19
|
+
from glpi_python_client.models.api_schema.enums import GlpiTimelinePosition
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GetDocumentItem(GlpiModel):
|
|
23
|
+
"""One ``Document_Item`` row linking a document to an item.
|
|
24
|
+
|
|
25
|
+
Keys the model does not declare (``users_id``, ``is_recursive``,
|
|
26
|
+
``links`` and any plugin column) are kept in ``extra_payload``.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
id : int
|
|
31
|
+
Native identifier of the link row.
|
|
32
|
+
documents_id : int
|
|
33
|
+
Identifier of the linked document.
|
|
34
|
+
itemtype : str | None, optional
|
|
35
|
+
GLPI type of the item the document is linked to, for example
|
|
36
|
+
``"Ticket"`` or ``"ITILFollowup"``.
|
|
37
|
+
items_id : int | None, optional
|
|
38
|
+
Identifier of the item the document is linked to.
|
|
39
|
+
entities_id : int | None, optional
|
|
40
|
+
Identifier of the entity owning the link.
|
|
41
|
+
date_creation : datetime | None, optional
|
|
42
|
+
Creation timestamp of the link row.
|
|
43
|
+
date_mod : datetime | None, optional
|
|
44
|
+
Last modification timestamp of the link row.
|
|
45
|
+
date : datetime | None, optional
|
|
46
|
+
Timestamp GLPI records on the link.
|
|
47
|
+
timeline_position : GlpiTimelinePosition | None, optional
|
|
48
|
+
Position of the link in the ticket timeline. A link at
|
|
49
|
+
:attr:`GlpiTimelinePosition.NO_TIMELINE` (``-1``) is written by
|
|
50
|
+
GLPI's editor for an image it embeds in a text. Such a link is
|
|
51
|
+
hidden from the ticket's Documents tab. A document uploaded as an
|
|
52
|
+
attachment is linked at ``1``.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
id: int
|
|
56
|
+
documents_id: int
|
|
57
|
+
itemtype: str | None = None
|
|
58
|
+
items_id: int | None = None
|
|
59
|
+
entities_id: int | None = None
|
|
60
|
+
date_creation: datetime | None = None
|
|
61
|
+
date_mod: datetime | None = None
|
|
62
|
+
date: datetime | None = None
|
|
63
|
+
timeline_position: GlpiTimelinePosition | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class FollowupDocuments(GlpiModel):
|
|
67
|
+
"""One ticket followup together with its document links.
|
|
68
|
+
|
|
69
|
+
Returned by ``list_ticket_followup_documents``. The followup is kept
|
|
70
|
+
whole, so a caller reads ``followup.is_private`` to classify every link
|
|
71
|
+
without a second read.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
followup : GetFollowup
|
|
76
|
+
The followup, as the ticket's followup listing returned it.
|
|
77
|
+
documents : list[GetDocumentItem]
|
|
78
|
+
The ``Document_Item`` rows linked to this followup. Empty when the
|
|
79
|
+
followup has no document.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
model_config = ConfigDict(extra="allow", frozen=True)
|
|
83
|
+
|
|
84
|
+
followup: GetFollowup
|
|
85
|
+
documents: list[GetDocumentItem]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = ["FollowupDocuments", "GetDocumentItem"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: glpi-python-client
|
|
3
|
-
Version: 0.6.
|
|
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=
|
|
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
|
|
@@ -21,7 +21,7 @@ glpi_python_client/_async/clients/api/assistance/__init__.py,sha256=nL77QpQ4Yzfd
|
|
|
21
21
|
glpi_python_client/_async/clients/api/assistance/_team.py,sha256=QEwFTXE8W_MURi_zgfDTLoqYP__NF1SwBd_7dLJZg5U,3824
|
|
22
22
|
glpi_python_client/_async/clients/api/assistance/_ticket.py,sha256=z1-kKjkSwvePZl65CXpDay50uJpirjlA1ww4_3eVyJU,7990
|
|
23
23
|
glpi_python_client/_async/clients/api/assistance/timeline/__init__.py,sha256=AqB_UpcRMHjfvZyLBjkwfsnEBwz77xBbGJzXzWmS7os,619
|
|
24
|
-
glpi_python_client/_async/clients/api/assistance/timeline/_document.py,sha256=
|
|
24
|
+
glpi_python_client/_async/clients/api/assistance/timeline/_document.py,sha256=BYCScaSyfrPzAh-znObYh1yEeL4dwYLYV8F0hKn6YcE,14803
|
|
25
25
|
glpi_python_client/_async/clients/api/assistance/timeline/_followup.py,sha256=jgE_kpJxMa3v1Tt4Jm2PahiRNX7v3vKh6xjbX27sp7w,6221
|
|
26
26
|
glpi_python_client/_async/clients/api/assistance/timeline/_solution.py,sha256=3DpbDeHDQ7FJdevhcpAcgXyB9FZu7_S2kVblFXsZUq0,6156
|
|
27
27
|
glpi_python_client/_async/clients/api/assistance/timeline/_task.py,sha256=EdkrRJnkHWaZdxgyqwL1rhUygGSJScR2jIS6CFJL4S4,5869
|
|
@@ -67,7 +67,7 @@ glpi_python_client/_sync/clients/api/assistance/__init__.py,sha256=dAXVQPgCmPhCL
|
|
|
67
67
|
glpi_python_client/_sync/clients/api/assistance/_team.py,sha256=Nk0FXUHyZZOkg62Os4FBee3Jv4YzMJoOqsvsWM0yqI0,3785
|
|
68
68
|
glpi_python_client/_sync/clients/api/assistance/_ticket.py,sha256=Xuk0-8geUdII-uFdgIbkhG_QMEfOytcHp5n-Hq2D-U8,7906
|
|
69
69
|
glpi_python_client/_sync/clients/api/assistance/timeline/__init__.py,sha256=d_YAwgv3r1sNrRVlPC5r1yEhbUZQKzLtuNzHD9eBF8Y,615
|
|
70
|
-
glpi_python_client/_sync/clients/api/assistance/timeline/_document.py,sha256=
|
|
70
|
+
glpi_python_client/_sync/clients/api/assistance/timeline/_document.py,sha256=0T7wEyVNoECI8c0LUZ1WQtXgvpRBKyl-GTsB4jdJ2xA,14709
|
|
71
71
|
glpi_python_client/_sync/clients/api/assistance/timeline/_followup.py,sha256=trQJJThA8jk2jVK-jTW8uPZyYEvR5Zi18mqGT6436VM,6158
|
|
72
72
|
glpi_python_client/_sync/clients/api/assistance/timeline/_solution.py,sha256=KVyrjKFS35lJzvjwEromOEL2Q2OfvLP7MvhV7QirtVs,6093
|
|
73
73
|
glpi_python_client/_sync/clients/api/assistance/timeline/_task.py,sha256=peSQmKYoHc9IVdQltBaDqFFHZKdBzWJwt_0GXng2odU,5806
|
|
@@ -95,13 +95,13 @@ 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=
|
|
99
|
-
glpi_python_client/models/__init__.py,sha256=
|
|
98
|
+
glpi_python_client/content/conversion.py,sha256=0dxQW0ojSo1KC8zcTFa476KQa1i3sLWzQ8gTEEVslys,45341
|
|
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
|
|
102
102
|
glpi_python_client/models/api_schema/_common.py,sha256=wVtaIkUM9iS3jyDb0bpFopEkP_3vP1ax7D8rwHe9QkI,2004
|
|
103
103
|
glpi_python_client/models/api_schema/_content.py,sha256=DlsEmlXIK_0jyXh3j5PgU6vkwch2o0I8UpUkLq2fqYw,12826
|
|
104
|
-
glpi_python_client/models/api_schema/enums.py,sha256=
|
|
104
|
+
glpi_python_client/models/api_schema/enums.py,sha256=bHNCuumOXIZm80n5RqIhCzFqizII-3er41mn2aktF5s,5071
|
|
105
105
|
glpi_python_client/models/api_schema/administration/__init__.py,sha256=uTr2rEMYq47T5foUMHm1rtsBmEQvdD1EQw5CZOUN38Y,706
|
|
106
106
|
glpi_python_client/models/api_schema/administration/_entity.py,sha256=KdbxMyPXumGZKmmabDIciF3nFS61pLRp5SBOTFa8slk,3297
|
|
107
107
|
glpi_python_client/models/api_schema/administration/_user.py,sha256=MQ6cHKdni98sjm-Whf0GKdGpNjPgGGJpUBgVjlGa19Q,15872
|
|
@@ -124,10 +124,11 @@ glpi_python_client/models/api_schema/knowledgebase/_article.py,sha256=zNLnkkdfGj
|
|
|
124
124
|
glpi_python_client/models/api_schema/knowledgebase/_category.py,sha256=bokNV56PTULFCAzV8dxLOAl8Nk2yCI5Lmb3RLGm0zTM,2260
|
|
125
125
|
glpi_python_client/models/api_schema/knowledgebase/_comment.py,sha256=mouYQE2AigGpJY0jTu9KoqOa4xT0_ckj0h6XDfY7mqc,2325
|
|
126
126
|
glpi_python_client/models/api_schema/knowledgebase/_revision.py,sha256=KuTb_F85mc63RHpQ1VFsmM-eDBjUrxAEdLM48-WmWYs,1985
|
|
127
|
-
glpi_python_client/models/api_schema/management/__init__.py,sha256=
|
|
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=
|
|
130
|
+
glpi_python_client/models/api_schema/management/_document.py,sha256=kiEkTnOydGHjOyAwTbo020Kei4ffkBDj-9m775gLyfw,5200
|
|
131
|
+
glpi_python_client/models/api_schema/management/_document_item.py,sha256=Bu9c9YdhSs5hGZ8dRfRxqS4g7VZ1A3EJtNbDLulKpWA,3113
|
|
131
132
|
glpi_python_client/models/api_schema/plugins/__init__.py,sha256=GA0pr0PeoPOqwjUilrBujh0OqjUfZhzPWgIlgDJcZog,544
|
|
132
133
|
glpi_python_client/models/api_schema/plugins/_fields.py,sha256=Se-12ZDqk_-on_37YF_hRD5S0e0H6zyha3SXI9xb_9Y,6576
|
|
133
134
|
glpi_python_client/models/custom_schema/__init__.py,sha256=Usp592x9OWws30vU1rEXbkkRBhcgytM5nxRPTB1gqhY,500
|
|
@@ -135,7 +136,7 @@ glpi_python_client/models/custom_schema/_ticket_context.py,sha256=jnlfgho-aDbmA9
|
|
|
135
136
|
glpi_python_client/testing/__init__.py,sha256=iLtUyJjyv3fEhy3PN25NxNTbtqDl3SoHLbNium0Z80U,568
|
|
136
137
|
glpi_python_client/testing/fixtures.py,sha256=HKt6MXHgcmLn4L98pSwp-oGlc9C--_vrABNe5G_iBeg,1286
|
|
137
138
|
glpi_python_client/testing/utils.py,sha256=JyO3w2d-0kYdtC1hEZ2Tu_YjnCz8nUQ_Ck05-1EhsX4,4404
|
|
138
|
-
glpi_python_client-0.6.
|
|
139
|
-
glpi_python_client-0.6.
|
|
140
|
-
glpi_python_client-0.6.
|
|
141
|
-
glpi_python_client-0.6.
|
|
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,,
|
|
File without changes
|
|
File without changes
|