glpi-python-client 0.2.0__py3-none-any.whl → 0.2.1__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 +1 -1
- glpi_python_client/models/custom_schema/_ticket_context.py +186 -0
- glpi_python_client/models/custom_schema/tests/test_ticket_context.py +170 -0
- {glpi_python_client-0.2.0.dist-info → glpi_python_client-0.2.1.dist-info}/METADATA +1 -1
- {glpi_python_client-0.2.0.dist-info → glpi_python_client-0.2.1.dist-info}/RECORD +7 -7
- {glpi_python_client-0.2.0.dist-info → glpi_python_client-0.2.1.dist-info}/WHEEL +0 -0
- {glpi_python_client-0.2.0.dist-info → glpi_python_client-0.2.1.dist-info}/licenses/LICENSE +0 -0
glpi_python_client/__init__.py
CHANGED
|
@@ -8,9 +8,14 @@ single object to reason about a ticket and its history.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
11
15
|
from pydantic import Field
|
|
12
16
|
|
|
13
17
|
from glpi_python_client.models._base import GlpiModel
|
|
18
|
+
from glpi_python_client.models.api_schema._common import IdNameRef
|
|
14
19
|
from glpi_python_client.models.api_schema.assistance._ticket import GetTicket
|
|
15
20
|
from glpi_python_client.models.api_schema.assistance.timeline._document import (
|
|
16
21
|
GetTimelineDocument,
|
|
@@ -25,6 +30,78 @@ from glpi_python_client.models.api_schema.assistance.timeline._task import (
|
|
|
25
30
|
GetTicketTask,
|
|
26
31
|
)
|
|
27
32
|
|
|
33
|
+
_MAX_DATETIME = datetime.max
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _ref_label(ref: IdNameRef | None) -> str | None:
|
|
37
|
+
"""Return the human-readable label of one ``IdNameRef`` reference.
|
|
38
|
+
|
|
39
|
+
The helper prefers ``name`` (the GLPI display label) and falls back to
|
|
40
|
+
the numeric identifier when the server only returned the foreign key.
|
|
41
|
+
Returns ``None`` when the reference itself is missing so callers can
|
|
42
|
+
omit the field from the rendered Markdown.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
if ref is None:
|
|
46
|
+
return None
|
|
47
|
+
if ref.name:
|
|
48
|
+
return ref.name
|
|
49
|
+
if ref.id is not None:
|
|
50
|
+
return f"#{ref.id}"
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _render_value(value: object | None) -> str | None:
|
|
55
|
+
"""Convert one supported metadata value into a display string.
|
|
56
|
+
|
|
57
|
+
The ticket-context Markdown view reuses one compact subtitle format
|
|
58
|
+
across the main ticket and every timeline item. This helper keeps
|
|
59
|
+
the formatting rules consistent for timestamps, GLPI references,
|
|
60
|
+
and enum values while leaving plain strings unchanged.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
if value is None:
|
|
64
|
+
return None
|
|
65
|
+
if isinstance(value, datetime):
|
|
66
|
+
return value.isoformat()
|
|
67
|
+
if isinstance(value, IdNameRef):
|
|
68
|
+
return _ref_label(value)
|
|
69
|
+
if isinstance(value, Enum):
|
|
70
|
+
return value.name.replace("_", " ").title()
|
|
71
|
+
return str(value)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _subtitle_line(*parts: tuple[str, object | None]) -> str | None:
|
|
75
|
+
"""Build one Markdown subtitle line from labeled metadata values.
|
|
76
|
+
|
|
77
|
+
Missing values are skipped so callers can pass the full set of
|
|
78
|
+
potentially interesting fields without having to pre-filter them.
|
|
79
|
+
The subtitle is emitted as one Markdown blockquote line to make the
|
|
80
|
+
metadata visually distinct from the section body.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
rendered_parts = []
|
|
84
|
+
for label, value in parts:
|
|
85
|
+
rendered_value = _render_value(value)
|
|
86
|
+
if rendered_value:
|
|
87
|
+
rendered_parts.append(f"{label}: {rendered_value}")
|
|
88
|
+
if not rendered_parts:
|
|
89
|
+
return None
|
|
90
|
+
return f"> {' | '.join(rendered_parts)}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _event_sort_key(event: Any) -> datetime:
|
|
94
|
+
"""Compute the sort key used to order timeline events for rendering.
|
|
95
|
+
|
|
96
|
+
Ticket context rendering must follow the actual activity chronology,
|
|
97
|
+
not the left/right anchoring hint used by the GLPI chat UI. Entries
|
|
98
|
+
are therefore always ordered by ``date_creation`` and items missing a
|
|
99
|
+
creation timestamp are pushed to the end while preserving the sort's
|
|
100
|
+
stability.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
return getattr(event, "date_creation", None) or _MAX_DATETIME
|
|
104
|
+
|
|
28
105
|
|
|
29
106
|
class GlpiTicketContext(GlpiModel):
|
|
30
107
|
"""Grouped public ticket context returned by ticket-context workflows.
|
|
@@ -49,5 +126,114 @@ class GlpiTicketContext(GlpiModel):
|
|
|
49
126
|
solutions: list[GetSolution] = Field(default_factory=list)
|
|
50
127
|
documents: list[GetTimelineDocument] = Field(default_factory=list)
|
|
51
128
|
|
|
129
|
+
def to_markdown(self) -> str:
|
|
130
|
+
"""Render the ticket and its timeline as one Markdown transcript.
|
|
131
|
+
|
|
132
|
+
The rendering starts with the ticket title, then a compact
|
|
133
|
+
subtitle line containing the requester, last editor, and the
|
|
134
|
+
key timestamps exposed by the public ticket model. The ticket
|
|
135
|
+
body is separated from the timeline itself, and each followup,
|
|
136
|
+
task, and solution receives its own heading plus a metadata
|
|
137
|
+
subtitle. Timeline entries are always sorted by ``date_creation``
|
|
138
|
+
so the transcript follows the actual chronology rather than the
|
|
139
|
+
GLPI UI anchoring hints. Linked documents are still appended in a
|
|
140
|
+
dedicated section because the document-link payload does not
|
|
141
|
+
expose the same authoring metadata.
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
str
|
|
146
|
+
Markdown transcript suitable for direct display or for
|
|
147
|
+
forwarding into a downstream Markdown renderer. The string
|
|
148
|
+
never ends with trailing whitespace.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
lines: list[str] = []
|
|
152
|
+
ticket = self.ticket
|
|
153
|
+
ticket_label = ticket.name or "(unnamed ticket)"
|
|
154
|
+
if ticket.id is not None:
|
|
155
|
+
lines.append(f"# Ticket #{ticket.id} \u2014 {ticket_label}")
|
|
156
|
+
else:
|
|
157
|
+
lines.append(f"# Ticket \u2014 {ticket_label}")
|
|
158
|
+
|
|
159
|
+
ticket_subtitle = _subtitle_line(
|
|
160
|
+
("Status", ticket.status),
|
|
161
|
+
("Requester", ticket.user_recipient),
|
|
162
|
+
("Last edited by", ticket.user_editor),
|
|
163
|
+
("Created at", ticket.date_creation),
|
|
164
|
+
("Updated at", ticket.date_mod),
|
|
165
|
+
("Resolved at", ticket.date_solve),
|
|
166
|
+
("Closed at", ticket.date_close),
|
|
167
|
+
)
|
|
168
|
+
if ticket_subtitle is not None:
|
|
169
|
+
lines.append(ticket_subtitle)
|
|
170
|
+
|
|
171
|
+
if ticket.content:
|
|
172
|
+
lines.append("")
|
|
173
|
+
lines.append("## Description")
|
|
174
|
+
lines.append("")
|
|
175
|
+
lines.append(ticket.content)
|
|
176
|
+
|
|
177
|
+
events: list[tuple[str, Any]] = []
|
|
178
|
+
events.extend(("Followup", item) for item in self.followups)
|
|
179
|
+
events.extend(("Task", item) for item in self.tasks)
|
|
180
|
+
events.extend(("Solution", item) for item in self.solutions)
|
|
181
|
+
events.sort(key=lambda pair: _event_sort_key(pair[1]))
|
|
182
|
+
|
|
183
|
+
if events:
|
|
184
|
+
lines.append("")
|
|
185
|
+
lines.append("## Timeline")
|
|
186
|
+
|
|
187
|
+
for kind, event in events:
|
|
188
|
+
event_id = getattr(event, "id", None)
|
|
189
|
+
heading = (
|
|
190
|
+
f"### {kind} #{event_id}" if event_id is not None else f"### {kind}"
|
|
191
|
+
)
|
|
192
|
+
lines.append("")
|
|
193
|
+
lines.append(heading)
|
|
194
|
+
|
|
195
|
+
event_subtitle = _subtitle_line(
|
|
196
|
+
("Created by", getattr(event, "user", None)),
|
|
197
|
+
("Last edited by", getattr(event, "user_editor", None)),
|
|
198
|
+
("Created at", getattr(event, "date_creation", None)),
|
|
199
|
+
("Updated at", getattr(event, "date_mod", None)),
|
|
200
|
+
("Scheduled for", getattr(event, "date", None)),
|
|
201
|
+
("Planned start", getattr(event, "planned_begin", None)),
|
|
202
|
+
("Planned end", getattr(event, "planned_end", None)),
|
|
203
|
+
("Approved at", getattr(event, "date_approval", None)),
|
|
204
|
+
("State", getattr(event, "state", None)),
|
|
205
|
+
("Status", getattr(event, "status", None)),
|
|
206
|
+
(
|
|
207
|
+
"Duration",
|
|
208
|
+
(
|
|
209
|
+
f"{duration}s"
|
|
210
|
+
if (duration := getattr(event, "duration", None)) is not None
|
|
211
|
+
else None
|
|
212
|
+
),
|
|
213
|
+
),
|
|
214
|
+
("Technician", getattr(event, "user_tech", None)),
|
|
215
|
+
("Technician group", getattr(event, "group_tech", None)),
|
|
216
|
+
("Approver", getattr(event, "approver", None)),
|
|
217
|
+
)
|
|
218
|
+
if event_subtitle is not None:
|
|
219
|
+
lines.append(event_subtitle)
|
|
220
|
+
|
|
221
|
+
content = getattr(event, "content", None)
|
|
222
|
+
if content:
|
|
223
|
+
lines.append("")
|
|
224
|
+
lines.append(content)
|
|
225
|
+
|
|
226
|
+
if self.documents:
|
|
227
|
+
lines.append("")
|
|
228
|
+
lines.append("## Documents")
|
|
229
|
+
for document in self.documents:
|
|
230
|
+
identifier = document.documents_id or document.id
|
|
231
|
+
label = document.filepath or (
|
|
232
|
+
f"document #{identifier}" if identifier is not None else "document"
|
|
233
|
+
)
|
|
234
|
+
lines.append(f"- {label}")
|
|
235
|
+
|
|
236
|
+
return "\n".join(lines).rstrip()
|
|
237
|
+
|
|
52
238
|
|
|
53
239
|
__all__ = ["GlpiTicketContext"]
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
|
|
5
7
|
import pytest
|
|
6
8
|
from pydantic import ValidationError
|
|
7
9
|
|
|
@@ -38,3 +40,171 @@ def test_ticket_context_accepts_timeline_records() -> None:
|
|
|
38
40
|
context = GlpiTicketContext.model_validate(payload)
|
|
39
41
|
assert context.tasks[0].id == 11
|
|
40
42
|
assert context.documents[0].documents_id == 99
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_to_markdown_renders_header_and_status() -> None:
|
|
46
|
+
"""``to_markdown`` includes the ticket id, name, status and content."""
|
|
47
|
+
|
|
48
|
+
context = GlpiTicketContext.model_validate(
|
|
49
|
+
{
|
|
50
|
+
"ticket": {
|
|
51
|
+
"id": 42,
|
|
52
|
+
"name": "Printer broken",
|
|
53
|
+
"content": "Cannot print",
|
|
54
|
+
"status": {"id": 2, "name": "Processing (assigned)"},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
rendered = context.to_markdown()
|
|
59
|
+
assert rendered.startswith("# Ticket #42 \u2014 Printer broken")
|
|
60
|
+
assert "> Status: Processing (assigned)" in rendered
|
|
61
|
+
assert "## Description" in rendered
|
|
62
|
+
assert "Cannot print" in rendered
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_to_markdown_renders_ticket_subtitle_metadata() -> None:
|
|
66
|
+
"""The main ticket subtitle includes requester, editor, and timestamps."""
|
|
67
|
+
|
|
68
|
+
context = GlpiTicketContext.model_validate(
|
|
69
|
+
{
|
|
70
|
+
"ticket": {
|
|
71
|
+
"id": 42,
|
|
72
|
+
"name": "Printer broken",
|
|
73
|
+
"user_recipient": {"id": 7, "name": "Alice"},
|
|
74
|
+
"user_editor": {"id": 8, "name": "Bob"},
|
|
75
|
+
"date_creation": datetime(2024, 1, 1, 9, 30, tzinfo=timezone.utc),
|
|
76
|
+
"date_mod": datetime(2024, 1, 2, 11, 45, tzinfo=timezone.utc),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
rendered = context.to_markdown()
|
|
82
|
+
|
|
83
|
+
assert "Requester: Alice" in rendered
|
|
84
|
+
assert "Last edited by: Bob" in rendered
|
|
85
|
+
assert "Created at: 2024-01-01T09:30:00+00:00" in rendered
|
|
86
|
+
assert "Updated at: 2024-01-02T11:45:00+00:00" in rendered
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_to_markdown_orders_events_by_creation_when_no_position() -> None:
|
|
90
|
+
"""Events without ``timeline_position`` are ordered by creation date."""
|
|
91
|
+
|
|
92
|
+
context = GlpiTicketContext.model_validate(
|
|
93
|
+
{
|
|
94
|
+
"ticket": {"id": 1, "name": "x"},
|
|
95
|
+
"followups": [
|
|
96
|
+
{
|
|
97
|
+
"id": 2,
|
|
98
|
+
"content": "second note",
|
|
99
|
+
"date_creation": datetime(2024, 1, 2, tzinfo=timezone.utc),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": 1,
|
|
103
|
+
"content": "first note",
|
|
104
|
+
"date_creation": datetime(2024, 1, 1, tzinfo=timezone.utc),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
rendered = context.to_markdown()
|
|
110
|
+
assert rendered.index("first note") < rendered.index("second note")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_to_markdown_ignores_timeline_position_for_ordering() -> None:
|
|
114
|
+
"""Timeline anchoring does not override chronological ordering."""
|
|
115
|
+
|
|
116
|
+
context = GlpiTicketContext.model_validate(
|
|
117
|
+
{
|
|
118
|
+
"ticket": {"id": 1, "name": "x"},
|
|
119
|
+
"followups": [
|
|
120
|
+
{
|
|
121
|
+
"id": 1,
|
|
122
|
+
"content": "no position late",
|
|
123
|
+
"date_creation": datetime(2024, 1, 5, tzinfo=timezone.utc),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
"tasks": [
|
|
127
|
+
{
|
|
128
|
+
"id": 2,
|
|
129
|
+
"content": "left positioned",
|
|
130
|
+
"timeline_position": 1,
|
|
131
|
+
"date_creation": datetime(2024, 1, 10, tzinfo=timezone.utc),
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
rendered = context.to_markdown()
|
|
137
|
+
assert rendered.index("no position late") < rendered.index("left positioned")
|
|
138
|
+
assert "## Timeline" in rendered
|
|
139
|
+
assert "### Task #2" in rendered
|
|
140
|
+
assert "### Followup #1" in rendered
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_to_markdown_renders_solution_and_documents() -> None:
|
|
144
|
+
"""Solutions and document links are rendered with their dedicated sections."""
|
|
145
|
+
|
|
146
|
+
context = GlpiTicketContext.model_validate(
|
|
147
|
+
{
|
|
148
|
+
"ticket": {"id": 7, "name": "Reset"},
|
|
149
|
+
"solutions": [{"id": 4, "content": "All fixed"}],
|
|
150
|
+
"documents": [
|
|
151
|
+
{"id": 11, "documents_id": 99, "filepath": "logs/run.txt"},
|
|
152
|
+
{"id": 12, "documents_id": 100},
|
|
153
|
+
],
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
rendered = context.to_markdown()
|
|
157
|
+
assert "### Solution #4" in rendered
|
|
158
|
+
assert "All fixed" in rendered
|
|
159
|
+
assert "## Documents" in rendered
|
|
160
|
+
assert "- logs/run.txt" in rendered
|
|
161
|
+
assert "- document #100" in rendered
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_to_markdown_handles_empty_timeline() -> None:
|
|
165
|
+
"""A ticket with no events still produces a valid Markdown header."""
|
|
166
|
+
|
|
167
|
+
context = GlpiTicketContext.model_validate({"ticket": {"id": 3, "name": "Quiet"}})
|
|
168
|
+
rendered = context.to_markdown()
|
|
169
|
+
assert rendered == "# Ticket #3 \u2014 Quiet"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_to_markdown_renders_task_duration() -> None:
|
|
173
|
+
"""Tasks expose their ``duration`` field in seconds."""
|
|
174
|
+
|
|
175
|
+
context = GlpiTicketContext.model_validate(
|
|
176
|
+
{
|
|
177
|
+
"ticket": {"id": 1, "name": "x"},
|
|
178
|
+
"tasks": [{"id": 9, "content": "work", "duration": 1800}],
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
rendered = context.to_markdown()
|
|
182
|
+
assert "> Duration: 1800s" in rendered
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_to_markdown_renders_event_creator_editor_and_timestamps() -> None:
|
|
186
|
+
"""Timeline subtitles include author, editor, and timestamp metadata."""
|
|
187
|
+
|
|
188
|
+
context = GlpiTicketContext.model_validate(
|
|
189
|
+
{
|
|
190
|
+
"ticket": {"id": 1, "name": "x"},
|
|
191
|
+
"followups": [
|
|
192
|
+
{
|
|
193
|
+
"id": 12,
|
|
194
|
+
"content": "note",
|
|
195
|
+
"user": {"id": 7, "name": "Alice"},
|
|
196
|
+
"user_editor": {"id": 8, "name": "Bob"},
|
|
197
|
+
"date_creation": datetime(2024, 1, 2, 10, 0, tzinfo=timezone.utc),
|
|
198
|
+
"date_mod": datetime(2024, 1, 2, 10, 5, tzinfo=timezone.utc),
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
rendered = context.to_markdown()
|
|
205
|
+
|
|
206
|
+
assert "### Followup #12" in rendered
|
|
207
|
+
assert "Created by: Alice" in rendered
|
|
208
|
+
assert "Last edited by: Bob" in rendered
|
|
209
|
+
assert "Created at: 2024-01-02T10:00:00+00:00" in rendered
|
|
210
|
+
assert "Updated at: 2024-01-02T10:05:00+00:00" in rendered
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glpi-python-client
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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=COSBZDaTdbd29TBO8qFMDCSIIf3AZELcnwHY_5QrAkY,2614
|
|
2
2
|
glpi_python_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
glpi_python_client/auth/__init__.py,sha256=rvvca0KTgvI2bkTSYX-h5KxpPpHQd2n9UFYl0nHAJ_Y,457
|
|
4
4
|
glpi_python_client/auth/_v1_session.py,sha256=R_SlKBrFJXCmXF0K3rmGAqkxH8cjaltItOJstcd-GVY,10263
|
|
@@ -81,13 +81,13 @@ glpi_python_client/models/api_schema/management/_document.py,sha256=Q-Tvo2fj9jzQ
|
|
|
81
81
|
glpi_python_client/models/api_schema/management/tests/__init__.py,sha256=c_aRMSPAlwOgz_YlkII5mvYgr780F4Du4eSnZn5ITpM,63
|
|
82
82
|
glpi_python_client/models/api_schema/management/tests/test_management_schemas.py,sha256=wMM8287WBhDUezKezCgINMyxqwjtYxpYRTKkRpmRNTI,1473
|
|
83
83
|
glpi_python_client/models/custom_schema/__init__.py,sha256=hB9OfuJzV6cF6jwvrLeT2CJhFHTqXYC5VlmwOoRknck,448
|
|
84
|
-
glpi_python_client/models/custom_schema/_ticket_context.py,sha256=
|
|
84
|
+
glpi_python_client/models/custom_schema/_ticket_context.py,sha256=G-W1IDY6JSv1FF2X2ntRKzso8GJAvPUcAlesv-MzdFc,8952
|
|
85
85
|
glpi_python_client/models/custom_schema/tests/__init__.py,sha256=62-bctnIbC39LbL18mCX10ItpxeqFvpORXslREcqjzs,55
|
|
86
|
-
glpi_python_client/models/custom_schema/tests/test_ticket_context.py,sha256=
|
|
86
|
+
glpi_python_client/models/custom_schema/tests/test_ticket_context.py,sha256=yZHQqFJGlg7X4rh8FIX6OAlNb4lKvF1yvirOXUf0WxQ,7195
|
|
87
87
|
glpi_python_client/testing/__init__.py,sha256=ilUPAUlbMa2NuXiRGZjC97argNKMnJ-heK_kf7fotkE,512
|
|
88
88
|
glpi_python_client/testing/fixtures.py,sha256=VQ-blVMyG706SR7SWuTW2UYr_nGa1qjKVnLwpsUFgHc,758
|
|
89
89
|
glpi_python_client/testing/utils.py,sha256=MtfHRKVDW_Lq8LrDaY9U6QaFvU-K0CnXMV4ZvJyVyzU,3054
|
|
90
|
-
glpi_python_client-0.2.
|
|
91
|
-
glpi_python_client-0.2.
|
|
92
|
-
glpi_python_client-0.2.
|
|
93
|
-
glpi_python_client-0.2.
|
|
90
|
+
glpi_python_client-0.2.1.dist-info/METADATA,sha256=M8mNSEvZZjLZIeKyO-n_2WgBERzX8cfW4GNi33qXd2M,6351
|
|
91
|
+
glpi_python_client-0.2.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
92
|
+
glpi_python_client-0.2.1.dist-info/licenses/LICENSE,sha256=nL8re-TIKkxCoL7OkSBja6EB4NsQR1HoOJgxv_MEhtc,1088
|
|
93
|
+
glpi_python_client-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|