glpi-python-client 0.6.3__py3-none-any.whl → 0.6.4__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/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_item.py +88 -0
- {glpi_python_client-0.6.3.dist-info → glpi_python_client-0.6.4.dist-info}/METADATA +1 -1
- {glpi_python_client-0.6.3.dist-info → glpi_python_client-0.6.4.dist-info}/RECORD +11 -10
- {glpi_python_client-0.6.3.dist-info → glpi_python_client-0.6.4.dist-info}/WHEEL +0 -0
- {glpi_python_client-0.6.3.dist-info → glpi_python_client-0.6.4.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.4"
|
|
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:
|
|
@@ -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",
|
|
@@ -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.4
|
|
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=IjuloET5lqy2KL5c3sgXRYxppjxcVAv7mZUK-DERRNA,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
|
|
@@ -96,12 +96,12 @@ glpi_python_client/_sync/clients/custom/_statistics.py,sha256=xrD5C99ieROJx60iUo
|
|
|
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
98
|
glpi_python_client/content/conversion.py,sha256=bglYY5IgoTv2SdS0Gton7WgsyzwwQ5GdU_2dLloL2l0,42860
|
|
99
|
-
glpi_python_client/models/__init__.py,sha256=
|
|
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
130
|
glpi_python_client/models/api_schema/management/_document.py,sha256=RzKF-rNlz8CsAMa2BAaiqHNEE3-m1mci7vtdARls4uw,4607
|
|
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.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,,
|
|
File without changes
|
|
File without changes
|