glpi-python-client 0.1.0__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.
Files changed (98) hide show
  1. glpi_python_client/__init__.py +36 -0
  2. glpi_python_client/auth/__init__.py +11 -0
  3. glpi_python_client/auth/auth.py +310 -0
  4. glpi_python_client/auth/tests/test_auth.py +189 -0
  5. glpi_python_client/clients/__init__.py +18 -0
  6. glpi_python_client/clients/api_v1_session.py +460 -0
  7. glpi_python_client/clients/api_v2_client.py +317 -0
  8. glpi_python_client/clients/async_api_v2_client.py +236 -0
  9. glpi_python_client/clients/tests/__init__.py +5 -0
  10. glpi_python_client/clients/tests/test_api_v1_session.py +85 -0
  11. glpi_python_client/clients/tests/test_api_v2_client.py +349 -0
  12. glpi_python_client/clients/tests/test_async_api_v2_client.py +257 -0
  13. glpi_python_client/clients/v2/__init__.py +8 -0
  14. glpi_python_client/clients/v2/async_/__init__.py +12 -0
  15. glpi_python_client/clients/v2/async_/api.py +29 -0
  16. glpi_python_client/clients/v2/async_/directory.py +88 -0
  17. glpi_python_client/clients/v2/async_/documents.py +144 -0
  18. glpi_python_client/clients/v2/async_/team.py +125 -0
  19. glpi_python_client/clients/v2/async_/tests/__init__.py +5 -0
  20. glpi_python_client/clients/v2/async_/tests/test_directory.py +43 -0
  21. glpi_python_client/clients/v2/async_/tests/test_documents.py +41 -0
  22. glpi_python_client/clients/v2/async_/tests/test_team.py +44 -0
  23. glpi_python_client/clients/v2/async_/tests/test_tickets.py +174 -0
  24. glpi_python_client/clients/v2/async_/tests/test_timeline.py +126 -0
  25. glpi_python_client/clients/v2/async_/tickets.py +312 -0
  26. glpi_python_client/clients/v2/async_/timeline.py +312 -0
  27. glpi_python_client/clients/v2/async_/transport.py +251 -0
  28. glpi_python_client/clients/v2/common/__init__.py +6 -0
  29. glpi_python_client/clients/v2/common/client_config.py +219 -0
  30. glpi_python_client/clients/v2/common/constants.py +45 -0
  31. glpi_python_client/clients/v2/common/errors.py +23 -0
  32. glpi_python_client/clients/v2/common/filters.py +30 -0
  33. glpi_python_client/clients/v2/common/payloads.py +57 -0
  34. glpi_python_client/clients/v2/common/request_http.py +195 -0
  35. glpi_python_client/clients/v2/common/response_payloads.py +76 -0
  36. glpi_python_client/clients/v2/common/ticket_search.py +113 -0
  37. glpi_python_client/clients/v2/sync/__init__.py +12 -0
  38. glpi_python_client/clients/v2/sync/api.py +29 -0
  39. glpi_python_client/clients/v2/sync/directory.py +90 -0
  40. glpi_python_client/clients/v2/sync/documents.py +144 -0
  41. glpi_python_client/clients/v2/sync/team.py +125 -0
  42. glpi_python_client/clients/v2/sync/tests/__init__.py +5 -0
  43. glpi_python_client/clients/v2/sync/tests/test_directory.py +57 -0
  44. glpi_python_client/clients/v2/sync/tests/test_documents.py +99 -0
  45. glpi_python_client/clients/v2/sync/tests/test_team.py +64 -0
  46. glpi_python_client/clients/v2/sync/tests/test_tickets.py +430 -0
  47. glpi_python_client/clients/v2/sync/tests/test_timeline.py +77 -0
  48. glpi_python_client/clients/v2/sync/tests/test_transport.py +89 -0
  49. glpi_python_client/clients/v2/sync/tickets.py +312 -0
  50. glpi_python_client/clients/v2/sync/timeline.py +308 -0
  51. glpi_python_client/clients/v2/sync/transport.py +246 -0
  52. glpi_python_client/content/__init__.py +11 -0
  53. glpi_python_client/content/conversion.py +58 -0
  54. glpi_python_client/content/records/__init__.py +84 -0
  55. glpi_python_client/content/records/core/__init__.py +6 -0
  56. glpi_python_client/content/records/core/document_links.py +100 -0
  57. glpi_python_client/content/records/core/normalization.py +53 -0
  58. glpi_python_client/content/records/core/references.py +98 -0
  59. glpi_python_client/content/records/core/scalars.py +83 -0
  60. glpi_python_client/content/records/parsers/__init__.py +6 -0
  61. glpi_python_client/content/records/parsers/directory.py +62 -0
  62. glpi_python_client/content/records/parsers/documents.py +49 -0
  63. glpi_python_client/content/records/parsers/team.py +58 -0
  64. glpi_python_client/content/records/parsers/tests/__init__.py +5 -0
  65. glpi_python_client/content/records/parsers/tests/test_tickets.py +35 -0
  66. glpi_python_client/content/records/parsers/tests/test_timeline.py +20 -0
  67. glpi_python_client/content/records/parsers/tickets.py +96 -0
  68. glpi_python_client/content/records/parsers/timeline.py +119 -0
  69. glpi_python_client/content/tests/__init__.py +5 -0
  70. glpi_python_client/content/tests/test_conversion.py +13 -0
  71. glpi_python_client/models/__init__.py +30 -0
  72. glpi_python_client/models/_base.py +22 -0
  73. glpi_python_client/models/_payload.py +79 -0
  74. glpi_python_client/models/_shared.py +37 -0
  75. glpi_python_client/models/glpi/__init__.py +27 -0
  76. glpi_python_client/models/glpi/_document.py +59 -0
  77. glpi_python_client/models/glpi/_followup.py +77 -0
  78. glpi_python_client/models/glpi/_location.py +53 -0
  79. glpi_python_client/models/glpi/_solution.py +57 -0
  80. glpi_python_client/models/glpi/_task.py +41 -0
  81. glpi_python_client/models/glpi/_team_member.py +33 -0
  82. glpi_python_client/models/glpi/_ticket.py +303 -0
  83. glpi_python_client/models/glpi/_user.py +92 -0
  84. glpi_python_client/models/glpi/tests/__init__.py +5 -0
  85. glpi_python_client/models/glpi/tests/test__document.py +12 -0
  86. glpi_python_client/models/glpi/tests/test__followup.py +31 -0
  87. glpi_python_client/models/glpi/tests/test__location.py +12 -0
  88. glpi_python_client/models/glpi/tests/test__solution.py +11 -0
  89. glpi_python_client/models/glpi/tests/test__ticket.py +59 -0
  90. glpi_python_client/models/glpi/tests/test__user.py +29 -0
  91. glpi_python_client/py.typed +0 -0
  92. glpi_python_client/testing/__init__.py +27 -0
  93. glpi_python_client/testing/fixtures.py +52 -0
  94. glpi_python_client/testing/utils.py +149 -0
  95. glpi_python_client-0.1.0.dist-info/METADATA +144 -0
  96. glpi_python_client-0.1.0.dist-info/RECORD +98 -0
  97. glpi_python_client-0.1.0.dist-info/WHEEL +4 -0
  98. glpi_python_client-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,79 @@
1
+ """Shared API payload serialization helpers for rich ticket objects.
2
+
3
+ These helpers keep payload cleanup and the uniform payload entrypoint in one
4
+ place for all models that can be sent back to GLPI.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from abc import ABC, abstractmethod
10
+ from collections.abc import Mapping
11
+ from typing import Any
12
+
13
+
14
+ def drop_empty_payload_values(payload: Mapping[str, object]) -> dict[str, object]:
15
+ """Return a payload without empty values that GLPI should not receive.
16
+
17
+ ``None``, empty mappings, and empty sequences are removed so outgoing API
18
+ payloads stay concise and avoid sending meaningless placeholders to GLPI.
19
+ """
20
+
21
+ return {
22
+ key: value
23
+ for key, value in payload.items()
24
+ if value is not None and value != {} and value != []
25
+ }
26
+
27
+
28
+ class ApiPayloadMixin(ABC):
29
+ """Mixin exposing one uniform API payload entrypoint.
30
+
31
+ Notes
32
+ -----
33
+ Subclasses must implement ``_build_api_payload`` with the keyword
34
+ arguments they require. The public ``to_api_payload`` method remains the
35
+ shared entrypoint used by transport clients.
36
+ """
37
+
38
+ def to_api_payload(self, *args: Any, **kwargs: Any) -> dict[str, object]:
39
+ """Return the raw API payload for the model.
40
+
41
+ Parameters
42
+ ----------
43
+ *args : object
44
+ Positional arguments forwarded to the payload builder.
45
+ **kwargs : object
46
+ Keyword arguments forwarded to the payload builder.
47
+
48
+ Returns
49
+ -------
50
+ dict[str, object]
51
+ Raw API payload.
52
+ """
53
+
54
+ payload = dict(self._build_api_payload(*args, **kwargs))
55
+ extra_payload = getattr(self, "extra_payload", None)
56
+ if not isinstance(extra_payload, Mapping) or not extra_payload:
57
+ return payload
58
+
59
+ merged_payload = dict(payload)
60
+ merged_payload.update(drop_empty_payload_values(extra_payload))
61
+ return drop_empty_payload_values(merged_payload)
62
+
63
+ @abstractmethod
64
+ def _build_api_payload(self, *args: Any, **kwargs: Any) -> Mapping[str, object]:
65
+ """Build the raw API payload for the concrete model.
66
+
67
+ Parameters
68
+ ----------
69
+ *args : object
70
+ Positional arguments supplied by the caller.
71
+ **kwargs : object
72
+ Keyword arguments supplied by the caller.
73
+
74
+ Returns
75
+ -------
76
+ dict[str, object]
77
+ Raw API payload.
78
+
79
+ """
@@ -0,0 +1,37 @@
1
+ """Shared helper functions for rich ticket normalization.
2
+
3
+ This module contains small model-normalization helpers used by the richer GLPI
4
+ objects when they accept either typed models or plain mappings.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from pydantic import BaseModel
12
+
13
+
14
+ def _model_data(value: object) -> dict[str, Any]:
15
+ """Normalize one model-like value to a plain mapping.
16
+
17
+ Parameters
18
+ ----------
19
+ value : object
20
+ Model-like value to normalize.
21
+
22
+ Returns
23
+ -------
24
+ dict[str, Any]
25
+ Plain mapping representation of the value.
26
+
27
+ Raises
28
+ ------
29
+ TypeError
30
+ Raised when the value is neither a Pydantic model nor a mapping.
31
+ """
32
+
33
+ if isinstance(value, BaseModel):
34
+ return value.model_dump(mode="python")
35
+ if isinstance(value, dict):
36
+ return dict(value)
37
+ raise TypeError(f"Unsupported model value {type(value)!r}")
@@ -0,0 +1,27 @@
1
+ """Concrete GLPI model exports used by the high-level v2 clients.
2
+
3
+ This subpackage groups the rich ticket-related models that the client returns
4
+ and accepts when interacting with GLPI resources.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from glpi_python_client.models.glpi._document import GlpiDocument
10
+ from glpi_python_client.models.glpi._followup import GlpiFollowup
11
+ from glpi_python_client.models.glpi._location import GlpiLocation
12
+ from glpi_python_client.models.glpi._solution import GlpiSolution
13
+ from glpi_python_client.models.glpi._task import GlpiTask
14
+ from glpi_python_client.models.glpi._team_member import GlpiTeamMember
15
+ from glpi_python_client.models.glpi._ticket import GlpiTicket
16
+ from glpi_python_client.models.glpi._user import GlpiUser
17
+
18
+ __all__ = [
19
+ "GlpiDocument",
20
+ "GlpiFollowup",
21
+ "GlpiLocation",
22
+ "GlpiSolution",
23
+ "GlpiTask",
24
+ "GlpiTeamMember",
25
+ "GlpiTicket",
26
+ "GlpiUser",
27
+ ]
@@ -0,0 +1,59 @@
1
+ """Typed GLPI document model.
2
+
3
+ The document model represents both fetched document metadata and the input data
4
+ needed by the legacy upload workflow.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from glpi_python_client.models._base import GlpiModel
10
+ from glpi_python_client.models._payload import ApiPayloadMixin
11
+
12
+
13
+ class GlpiDocument(ApiPayloadMixin, GlpiModel):
14
+ """GLPI document rich object.
15
+
16
+ Parameters
17
+ ----------
18
+ document_id : str | None, optional
19
+ Native GLPI document identifier.
20
+ filename : str | None, optional
21
+ Document filename.
22
+ mime_type : str | None, optional
23
+ MIME type.
24
+ linked_document_id : str | None, optional
25
+ Linked GLPI document identifier when different from ``document_id``.
26
+ ticket_id : int | None, optional
27
+ Owning ticket ID for uploads.
28
+ content : bytes | None, optional
29
+ Binary payload used for uploads.
30
+ document_name : str | None, optional
31
+ GLPI document title used during upload.
32
+ """
33
+
34
+ document_id: str | None = None
35
+ filename: str | None = None
36
+ mime_type: str | None = None
37
+ linked_document_id: str | None = None
38
+ ticket_id: int | str | None = None
39
+ content: bytes | None = None
40
+ document_name: str | None = None
41
+
42
+ def _build_api_payload(self) -> dict[str, object]:
43
+ """Return the raw upload payload consumed by the GLPI document gateway.
44
+
45
+ Returns
46
+ -------
47
+ dict[str, object]
48
+ Raw upload payload.
49
+ """
50
+
51
+ return {
52
+ "ticket_id": self.ticket_id,
53
+ "filename": self.filename,
54
+ "content": self.content,
55
+ "mime_type": self.mime_type
56
+ if self.mime_type is not None
57
+ else "application/octet-stream",
58
+ "document_name": self.document_name,
59
+ }
@@ -0,0 +1,77 @@
1
+ """Typed GLPI followup model.
2
+
3
+ The followup model stores normalized Markdown content and the metadata returned
4
+ by GLPI timeline endpoints.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+
11
+ from glpi_python_client.content.conversion import GlpiContentConverter
12
+ from glpi_python_client.models._base import GlpiModel
13
+ from glpi_python_client.models._payload import (
14
+ ApiPayloadMixin,
15
+ drop_empty_payload_values,
16
+ )
17
+ from glpi_python_client.models.glpi._user import GlpiUser
18
+
19
+
20
+ class GlpiFollowup(ApiPayloadMixin, GlpiModel):
21
+ """GLPI followup rich object.
22
+
23
+ Parameters
24
+ ----------
25
+ content : str | None, optional
26
+ Followup body in canonical Markdown.
27
+ followup_id : str | None, optional
28
+ Native GLPI followup identifier.
29
+ created_at : datetime | None, optional
30
+ Creation timestamp.
31
+ updated_at : datetime | None, optional
32
+ Last update timestamp.
33
+ author : GlpiUser | None, optional
34
+ Followup author.
35
+ editor : GlpiUser | None, optional
36
+ Last editor.
37
+ is_private : bool, optional
38
+ Whether the followup is private.
39
+ attachment_document_ids : tuple[str, ...], optional
40
+ GLPI document identifiers referenced by the followup.
41
+ """
42
+
43
+ followup_id: str | None = None
44
+ content: str | None = None
45
+ created_at: datetime | None = None
46
+ updated_at: datetime | None = None
47
+ author: GlpiUser | None = None
48
+ editor: GlpiUser | None = None
49
+ is_private: bool = False
50
+ attachment_document_ids: tuple[str, ...] = ()
51
+
52
+ def _build_api_payload(self) -> dict[str, object]:
53
+ """Return the raw GLPI API request body for the followup.
54
+
55
+ Returns
56
+ -------
57
+ dict[str, object]
58
+ Raw followup API request body.
59
+ """
60
+
61
+ payload: dict[str, object] = {
62
+ "content": GlpiContentConverter.to_transport(self.content),
63
+ "is_private": self.is_private,
64
+ }
65
+ author_id = getattr(self.author, "user_id", None)
66
+ if author_id is not None:
67
+ payload["user"] = {"id": author_id}
68
+ editor_id = getattr(self.editor, "user_id", None)
69
+ if editor_id is not None:
70
+ payload["user_editor"] = {"id": editor_id}
71
+ if self.created_at is not None:
72
+ timestamp = self.created_at.strftime("%Y-%m-%d %H:%M:%S")
73
+ payload["date_creation"] = timestamp
74
+ payload["date"] = timestamp
75
+ if self.updated_at is not None:
76
+ payload["date_mod"] = self.updated_at.strftime("%Y-%m-%d %H:%M:%S")
77
+ return drop_empty_payload_values(payload)
@@ -0,0 +1,53 @@
1
+ """Typed GLPI location model.
2
+
3
+ The location model is used both for parsed directory responses and for outgoing
4
+ location-creation payloads.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from glpi_python_client.models._base import GlpiModel
10
+ from glpi_python_client.models._payload import (
11
+ ApiPayloadMixin,
12
+ drop_empty_payload_values,
13
+ )
14
+
15
+
16
+ class GlpiLocation(ApiPayloadMixin, GlpiModel):
17
+ """GLPI location rich object.
18
+
19
+ Parameters
20
+ ----------
21
+ location_id : str | None, optional
22
+ Native GLPI location identifier.
23
+ name : str | None, optional
24
+ Location display name.
25
+ entity_id : int | None, optional
26
+ Owning GLPI entity.
27
+ """
28
+
29
+ location_id: str | None = None
30
+ name: str | None = None
31
+ entity_id: int | None = None
32
+
33
+ def _build_api_payload(self) -> dict[str, object]:
34
+ """Return the raw GLPI location-create request body.
35
+
36
+ Returns
37
+ -------
38
+ dict[str, object]
39
+ Raw GLPI location-create request body.
40
+
41
+ """
42
+
43
+ location_id = self.location_id
44
+
45
+ return drop_empty_payload_values(
46
+ {
47
+ "id": location_id,
48
+ "name": self.name,
49
+ "entity": {"id": self.entity_id}
50
+ if self.entity_id is not None
51
+ else None,
52
+ }
53
+ )
@@ -0,0 +1,57 @@
1
+ """Typed GLPI solution model.
2
+
3
+ The solution model stores normalized Markdown content together with the metadata
4
+ returned by GLPI solution timeline endpoints.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+
11
+ from glpi_python_client.content.conversion import GlpiContentConverter
12
+ from glpi_python_client.models._base import GlpiModel
13
+ from glpi_python_client.models._payload import (
14
+ ApiPayloadMixin,
15
+ drop_empty_payload_values,
16
+ )
17
+ from glpi_python_client.models.glpi._user import GlpiUser
18
+
19
+
20
+ class GlpiSolution(ApiPayloadMixin, GlpiModel):
21
+ """GLPI solution rich object.
22
+
23
+ Parameters
24
+ ----------
25
+ content : str | None, optional
26
+ Solution body in canonical Markdown.
27
+ solution_id : str | None, optional
28
+ Native GLPI solution identifier.
29
+ created_at : datetime | None, optional
30
+ Creation timestamp.
31
+ updated_at : datetime | None, optional
32
+ Last update timestamp.
33
+ author : GlpiUser | None, optional
34
+ Solution author.
35
+ attachment_document_ids : tuple[str, ...], optional
36
+ GLPI document identifiers referenced by the solution.
37
+ """
38
+
39
+ solution_id: str | None = None
40
+ content: str | None = None
41
+ created_at: datetime | None = None
42
+ updated_at: datetime | None = None
43
+ author: GlpiUser | None = None
44
+ attachment_document_ids: tuple[str, ...] = ()
45
+
46
+ def _build_api_payload(self) -> dict[str, object]:
47
+ """Return the raw GLPI API request body for the solution.
48
+
49
+ Returns
50
+ -------
51
+ dict[str, object]
52
+ Raw solution API request body.
53
+ """
54
+
55
+ return drop_empty_payload_values(
56
+ {"content": GlpiContentConverter.to_transport(self.content)}
57
+ )
@@ -0,0 +1,41 @@
1
+ """Typed GLPI task model.
2
+
3
+ The task model represents parsed task timeline entries returned by GLPI.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+
10
+ from glpi_python_client.models._base import GlpiModel
11
+ from glpi_python_client.models.glpi._user import GlpiUser
12
+
13
+
14
+ class GlpiTask(GlpiModel):
15
+ """GLPI task rich object.
16
+
17
+ Parameters
18
+ ----------
19
+ content : str | None, optional
20
+ Task body in canonical Markdown.
21
+ task_id : str | None, optional
22
+ Native GLPI task identifier.
23
+ created_at : datetime | None, optional
24
+ Creation timestamp.
25
+ updated_at : datetime | None, optional
26
+ Last update timestamp.
27
+ author : GlpiUser | None, optional
28
+ Task author.
29
+ editor : GlpiUser | None, optional
30
+ Last editor.
31
+ is_private : bool, optional
32
+ Whether the task is private.
33
+ """
34
+
35
+ task_id: str | None = None
36
+ content: str | None = None
37
+ created_at: datetime | None = None
38
+ updated_at: datetime | None = None
39
+ author: GlpiUser | None = None
40
+ editor: GlpiUser | None = None
41
+ is_private: bool = False
@@ -0,0 +1,33 @@
1
+ """Typed GLPI team-member model.
2
+
3
+ The team-member model is used by the ticket team helpers for both parsed team
4
+ associations and outgoing membership mutations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from glpi_python_client.models._base import GlpiModel
10
+
11
+
12
+ class GlpiTeamMember(GlpiModel):
13
+ """GLPI team-member rich object.
14
+
15
+ Parameters
16
+ ----------
17
+ member_type : str
18
+ GLPI member type such as ``"User"`` or ``"Group"``.
19
+ member_id : int
20
+ Native GLPI member identifier when known.
21
+ role : str
22
+ Team role carried by the member.
23
+ name : str | None, optional
24
+ Optional member name.
25
+ display_name : str | None, optional
26
+ Optional display name.
27
+ """
28
+
29
+ member_type: str
30
+ member_id: int
31
+ role: str
32
+ name: str | None = None
33
+ display_name: str | None = None