glpi-python-client 0.3.2__tar.gz → 0.3.3__tar.gz

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 (129) hide show
  1. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/.gitignore +3 -2
  2. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/PKG-INFO +1 -1
  3. glpi_python_client-0.3.3/docs/development_rtd.rst +162 -0
  4. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/user_guide.rst +15 -3
  5. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/__init__.py +1 -1
  6. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/assistance/timeline/_document.py +21 -20
  7. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/_statistics.py +21 -16
  8. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/_statistics_async.py +19 -14
  9. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/tests/test_statistics.py +2 -2
  10. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/tests/test_api_coverage.py +2 -2
  11. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/custom_schema/_ticket_context.py +12 -9
  12. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/custom_schema/tests/test_ticket_context.py +7 -7
  13. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/pyproject.toml +1 -1
  14. glpi_python_client-0.3.2/docs/development_rtd.rst +0 -84
  15. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/.pre-commit-config.yaml +0 -0
  16. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/.readthedocs.yaml +0 -0
  17. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/CONTRIBUTING.md +0 -0
  18. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/LICENSE +0 -0
  19. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/README.md +0 -0
  20. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/_static/.gitkeep +0 -0
  21. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/api_reference.rst +0 -0
  22. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/conf.py +0 -0
  23. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/development.md +0 -0
  24. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/glpi_api_contract.json +0 -0
  25. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/index.rst +0 -0
  26. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/installation.rst +0 -0
  27. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/publishing.md +0 -0
  28. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/publishing_rtd.rst +0 -0
  29. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/docs/sponsoring.rst +0 -0
  30. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/auth/__init__.py +0 -0
  31. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/auth/_v1_session.py +0 -0
  32. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/auth/auth.py +0 -0
  33. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/auth/tests/test_auth.py +0 -0
  34. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/auth/tests/test_v1_session.py +0 -0
  35. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/__init__.py +0 -0
  36. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/__init__.py +0 -0
  37. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/administration/__init__.py +0 -0
  38. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/administration/_entity.py +0 -0
  39. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/administration/_user.py +0 -0
  40. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/assistance/__init__.py +0 -0
  41. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/assistance/_team.py +0 -0
  42. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/assistance/_ticket.py +0 -0
  43. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/assistance/timeline/__init__.py +0 -0
  44. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/assistance/timeline/_followup.py +0 -0
  45. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/assistance/timeline/_solution.py +0 -0
  46. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/assistance/timeline/_task.py +0 -0
  47. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/dropdowns/__init__.py +0 -0
  48. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/dropdowns/_location.py +0 -0
  49. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/management/__init__.py +0 -0
  50. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/api/management/_document.py +0 -0
  51. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/async_client.py +0 -0
  52. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/__init__.py +0 -0
  53. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/_async_bridge.py +0 -0
  54. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/_config.py +0 -0
  55. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/_constants.py +0 -0
  56. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/_errors.py +0 -0
  57. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/_filters.py +0 -0
  58. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/_http.py +0 -0
  59. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/_payloads.py +0 -0
  60. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/_transport.py +0 -0
  61. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/tests/__init__.py +0 -0
  62. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/tests/test_errors.py +0 -0
  63. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/tests/test_filters.py +0 -0
  64. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/tests/test_http.py +0 -0
  65. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/tests/test_payloads.py +0 -0
  66. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/commons/tests/test_transport.py +0 -0
  67. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/__init__.py +0 -0
  68. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/_pagination_async.py +0 -0
  69. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/_ticket_context.py +0 -0
  70. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/_ticket_context_async.py +0 -0
  71. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/tests/test_statistics_async.py +0 -0
  72. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/custom/tests/test_ticket_context.py +0 -0
  73. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/sync_client.py +0 -0
  74. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/tests/__init__.py +0 -0
  75. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/tests/test_async_branches.py +0 -0
  76. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/tests/test_async_smoke.py +0 -0
  77. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/tests/test_glpi_client.py +0 -0
  78. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/tests/test_parity.py +0 -0
  79. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/clients/tests/test_smoke.py +0 -0
  80. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/content/__init__.py +0 -0
  81. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/content/conversion.py +0 -0
  82. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/content/tests/__init__.py +0 -0
  83. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/content/tests/test_conversion.py +0 -0
  84. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/__init__.py +0 -0
  85. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/_base.py +0 -0
  86. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/__init__.py +0 -0
  87. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/_common.py +0 -0
  88. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/_content.py +0 -0
  89. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/administration/__init__.py +0 -0
  90. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/administration/_entity.py +0 -0
  91. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/administration/_user.py +0 -0
  92. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/administration/tests/__init__.py +0 -0
  93. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/administration/tests/test_administration_schemas.py +0 -0
  94. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/__init__.py +0 -0
  95. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/_team.py +0 -0
  96. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/_ticket.py +0 -0
  97. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/tests/__init__.py +0 -0
  98. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/tests/test_assistance_schemas.py +0 -0
  99. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/tests/test_content_roundtrip.py +0 -0
  100. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/timeline/__init__.py +0 -0
  101. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/timeline/_document.py +0 -0
  102. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/timeline/_followup.py +0 -0
  103. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/timeline/_solution.py +0 -0
  104. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/timeline/_task.py +0 -0
  105. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/timeline/tests/__init__.py +0 -0
  106. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/assistance/timeline/tests/test_timeline_schemas.py +0 -0
  107. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/dropdowns/__init__.py +0 -0
  108. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/dropdowns/_location.py +0 -0
  109. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/dropdowns/tests/__init__.py +0 -0
  110. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/dropdowns/tests/test_dropdowns_schemas.py +0 -0
  111. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/enums.py +0 -0
  112. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/management/__init__.py +0 -0
  113. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/management/_document.py +0 -0
  114. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/management/tests/__init__.py +0 -0
  115. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/api_schema/management/tests/test_management_schemas.py +0 -0
  116. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/custom_schema/__init__.py +0 -0
  117. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/models/custom_schema/tests/__init__.py +0 -0
  118. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/py.typed +0 -0
  119. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/testing/__init__.py +0 -0
  120. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/testing/fixtures.py +0 -0
  121. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/glpi_python_client/testing/utils.py +0 -0
  122. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/skills/README.md +0 -0
  123. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/skills/glpi-client-setup/SKILL.md +0 -0
  124. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/skills/glpi-document-workflow/SKILL.md +0 -0
  125. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/skills/glpi-reporting-and-context/SKILL.md +0 -0
  126. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/skills/glpi-team-members/SKILL.md +0 -0
  127. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/skills/glpi-ticket-timeline/SKILL.md +0 -0
  128. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/skills/glpi-ticket-workflow/SKILL.md +0 -0
  129. {glpi_python_client-0.3.2 → glpi_python_client-0.3.3}/skills/glpi-user-location-provisioning/SKILL.md +0 -0
@@ -26,5 +26,6 @@ dist/
26
26
  secrets/
27
27
  secrets/*
28
28
 
29
- integration_tests/
30
- integration_tests/*
29
+ .claude/
30
+
31
+ CLAUDE.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glpi-python-client
3
- Version: 0.3.2
3
+ Version: 0.3.3
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/
@@ -0,0 +1,162 @@
1
+ Development
2
+ ===========
3
+
4
+ Local Setup
5
+ -----------
6
+
7
+ Create a virtual environment and install the development dependencies:
8
+
9
+ .. code-block:: console
10
+
11
+ python -m venv .venv
12
+ .venv\Scripts\activate
13
+ python -m pip install --upgrade pip
14
+ python -m pip install -e .[dev]
15
+ python -m pre_commit install
16
+
17
+ The repository ships a root ``.pre-commit-config.yaml`` that runs Ruff on each
18
+ commit. The lint hook applies safe fixes first, then Ruff formats the touched
19
+ files.
20
+
21
+ Quality Checks
22
+ --------------
23
+
24
+ Run the focused checks before opening a pull request:
25
+
26
+ .. code-block:: console
27
+
28
+ python -m pre_commit run --all-files
29
+ python -m pytest
30
+ python -m ruff check .
31
+ python -m mypy glpi_python_client
32
+ python -m sphinx -b html docs docs/_build/html
33
+
34
+ Integration Tests
35
+ -----------------
36
+
37
+ The ``integration_tests/`` directory holds end-to-end tests that drive a live
38
+ GLPI instance through the synchronous and asynchronous clients. They are
39
+ collected only when secrets resolve to a reachable instance; otherwise each
40
+ test self-skips via ``pytest.skip``. Every test cleans up the records it
41
+ creates in a ``finally`` block, but it is still recommended to point them at
42
+ a non-production GLPI.
43
+
44
+ Markers and CI behaviour
45
+ ~~~~~~~~~~~~~~~~~~~~~~~~
46
+
47
+ All integration tests are tagged with the ``integration`` pytest marker
48
+ declared in ``pyproject.toml``. The ``ci.yml`` workflow runs
49
+ ``pytest -m "not integration"`` for both the test matrix and the coverage
50
+ job, so the public CI never reaches a live GLPI. The default local
51
+ ``python -m pytest`` invocation *does* attempt to collect them, but they
52
+ skip automatically when no secrets are configured.
53
+
54
+ To explicitly opt in or out locally:
55
+
56
+ .. code-block:: console
57
+
58
+ python -m pytest -m integration # run only the live suite
59
+ python -m pytest -m "not integration" # mirror the CI behaviour
60
+
61
+ Configuration
62
+ ~~~~~~~~~~~~~
63
+
64
+ The suite reads each value from a file named after the secret under
65
+ ``secrets/`` at the repository root, falling back to the matching
66
+ environment variable when the file is absent. The ``secrets/`` directory is
67
+ gitignored. Each file contains a single trimmed value.
68
+
69
+ Required:
70
+
71
+ ============================== ========================= ===========================================
72
+ Secret file Environment variable Purpose
73
+ ============================== ========================= ===========================================
74
+ ``glpi_api_url`` ``GLPI_API_URL`` Base URL of the GLPI v2 API.
75
+ ``glpi_client_id_test`` ``GLPI_CLIENT_ID`` OAuth2 client identifier.
76
+ ``glpi_client_secret_test`` ``GLPI_CLIENT_SECRET`` OAuth2 client secret.
77
+ ``glpi_username`` ``GLPI_USERNAME`` GLPI user for the password grant.
78
+ ``glpi_password`` ``GLPI_PASSWORD`` Password for the GLPI user above.
79
+ ============================== ========================= ===========================================
80
+
81
+ Optional:
82
+
83
+ ============================== ============================= ============================================
84
+ Secret file Environment variable Purpose
85
+ ============================== ============================= ============================================
86
+ ``glpi_verify_ssl`` ``GLPI_VERIFY_SSL`` Toggle TLS verification (default ``false``).
87
+ ``glpi_entity`` ``GLPI_ENTITY`` Active entity id sent on every request.
88
+ ``glpi_profile`` ``GLPI_PROFILE`` Active profile id sent on every request.
89
+ ``glpi_entity_recursive`` ``GLPI_ENTITY_RECURSIVE`` Include sub-entities (default ``false``).
90
+ ``glpi_api_v1_url`` ``GLPI_API_V1_URL`` Base URL of the legacy v1 API.
91
+ ``glpi_api_v1_token_user`` ``GLPI_V1_USER_TOKEN`` v1 user token (enables document uploads).
92
+ ``glpi_api_v1_app_token`` ``GLPI_V1_APP_TOKEN`` v1 application token paired with the above.
93
+ ``glpi_team_member_role`` ``GLPI_TEAM_MEMBER_ROLE`` Role used to add the test user to a ticket.
94
+ ============================== ============================= ============================================
95
+
96
+ The v1 secrets are only required for the document-upload test; when they
97
+ are missing that single test skips while the rest of the suite runs.
98
+
99
+ Running the suite
100
+ ~~~~~~~~~~~~~~~~~
101
+
102
+ Once secrets are in place:
103
+
104
+ .. code-block:: console
105
+
106
+ python -m pytest integration_tests -m integration
107
+
108
+ Use a disposable GLPI instance or one whose entity is dedicated to
109
+ automated tests. The suite creates and deletes users, locations, tickets,
110
+ followups, tasks, and solutions on every run.
111
+
112
+ Package Layout
113
+ --------------
114
+
115
+ ``glpi_python_client.__init__``
116
+ Public import surface (``GlpiClient``, ``GlpiTicketContext``, public Pydantic
117
+ models, enums, and ``__version__``).
118
+
119
+ ``glpi_python_client.clients.glpi_client``
120
+ Composition root. ``GlpiClient`` mixes the per-resource async API mixins,
121
+ the OAuth2 token manager, the asynchronous v2 transport, and the optional
122
+ internal v1 session used for document uploads.
123
+
124
+ ``glpi_python_client.clients.api``
125
+ Async API mixins generated from the GLPI v2 OpenAPI contract: tickets,
126
+ ticket timeline (followups, tasks, solutions, documents), team members,
127
+ documents, users, locations, entities, ...
128
+
129
+ ``glpi_python_client.clients.custom``
130
+ Higher-level helpers built on top of the contract mixins:
131
+ ``get_ticket_context``, ``get_ticket_statistics``, ``get_task_statistics``.
132
+
133
+ ``glpi_python_client.clients.commons``
134
+ Shared HTTP transport pieces, including the timeline envelope unwrap that
135
+ reconciles live server behaviour with the OpenAPI contract.
136
+
137
+ ``glpi_python_client.models.api_schema``
138
+ Contract-aligned Pydantic v2 models (``Get``/``Post``/``Patch``/``Delete``)
139
+ for each GLPI v2 resource.
140
+
141
+ ``glpi_python_client.models.custom_schema``
142
+ Composite models such as ``GlpiTicketContext`` returned by the custom
143
+ helpers.
144
+
145
+ Adding Endpoints
146
+ ----------------
147
+
148
+ #. Add or extend the contract-aligned models in
149
+ ``glpi_python_client.models.api_schema``.
150
+ #. Add the async mixin and method under ``glpi_python_client.clients.api``,
151
+ mirroring the OpenAPI path and HTTP verb.
152
+ #. When the live server diverges from the contract, document the choice in the
153
+ module docstring and (when needed) wire an unwrap helper from
154
+ ``glpi_python_client.clients.commons``.
155
+ #. Re-export new public symbols from ``glpi_python_client.__init__``.
156
+ #. Add tests for payload serialization, response parsing, and client behaviour.
157
+ #. Document the workflow in :doc:`user_guide` and the matching skill in
158
+ ``skills/``.
159
+
160
+ Keep organization-specific entity, profile, and category defaults outside the
161
+ library core. Applications can apply their own mapping before calling the
162
+ client.
@@ -776,6 +776,14 @@ batches until the API returns fewer rows than the requested
776
776
  print(ticket.id, ticket.name)
777
777
  print(f"processed {total} tickets")
778
778
 
779
+ .. note::
780
+
781
+ Always pass an RSQL filter to ``iter_search_tickets``. Querying
782
+ without any filter can return very large result sets and may cause
783
+ the GLPI server to return a 500 error on busy instances. The other
784
+ two generators (``iter_search_users``, ``iter_search_entities``) are
785
+ not affected because those collections are typically much smaller.
786
+
779
787
  On the asynchronous client the same helpers are exposed as **async
780
788
  generators** through the bridge, so each ``next()`` call runs off the
781
789
  event loop and the consumer uses ``async for``:
@@ -790,8 +798,10 @@ event loop and the consumer uses ``async for``:
790
798
  ^^^^^^^^^^^^^^^^^^^^^^^^^
791
799
 
792
800
  Counts tickets created within an ISO date window and groups them by
793
- entity, status, priority, and type. Optional filters restrict the
794
- result set on the server side:
801
+ entity, status, priority, and type. The ``start_date`` is inclusive
802
+ from 00:00:00 and the ``end_date`` is inclusive through 23:59:59, so
803
+ tickets created at any time on those days are counted. Optional
804
+ filters restrict the result set on the server side:
795
805
 
796
806
  * ``entity_id`` — restrict to a single entity by numeric identifier.
797
807
  * ``entity_name`` — substring match against the entity ``name`` column;
@@ -893,7 +903,9 @@ then computes per-user and per-entity totals.
893
903
  Available filters:
894
904
 
895
905
  * ``start_date`` / ``end_date`` / ``default_days`` — ISO ``YYYY-MM-DD``
896
- date window; ``default_days`` is used when ``start_date`` is omitted.
906
+ date window; ``start_date`` is inclusive from 00:00:00,
907
+ ``end_date`` is inclusive through 23:59:59, and ``default_days``
908
+ is used when ``start_date`` is omitted.
897
909
  * ``entity_id`` — restrict to a single entity by identifier.
898
910
  * ``entity_name`` — substring match resolved through ``search_entities``;
899
911
  ignored when ``entity_id`` is given.
@@ -72,7 +72,7 @@ from glpi_python_client.models import (
72
72
  TicketMarkdownOptions,
73
73
  )
74
74
 
75
- __version__ = "0.3.2"
75
+ __version__ = "0.3.3"
76
76
 
77
77
  __all__ = [
78
78
  "AsyncGlpiClient",
@@ -7,11 +7,12 @@ Notes
7
7
  -----
8
8
  The live GLPI v2 server returns each entry of the list endpoint wrapped
9
9
  in a ``{"type": "Document_Item", "item": {...}}`` envelope, even though
10
- the OpenAPI contract documents a flat array of ``Document_Item``. Real
11
- behaviour wins over the contract, so :func:`list_ticket_timeline_documents`
12
- unwraps the envelope through the shared
13
- :meth:`~glpi_python_client.clients.commons._transport.TransportMixin._resource_list`
14
- helper and tolerates both shapes.
10
+ the OpenAPI contract documents a flat array of ``Document_Item``. The
11
+ ``item`` value is a full ``Document`` record (matching :class:`GetDocument`),
12
+ not a ``Document_Item`` link record — real behaviour wins over the contract.
13
+ :func:`list_ticket_timeline_documents` unwraps the envelope through the shared
14
+ ``TransportMixin._resource_list`` helper and deserialises each inner object
15
+ as :class:`GetDocument`.
15
16
  """
16
17
 
17
18
  from __future__ import annotations
@@ -24,19 +25,17 @@ from glpi_python_client.clients.commons._constants import (
24
25
  from glpi_python_client.clients.commons._transport import TransportMixin
25
26
  from glpi_python_client.models.api_schema.assistance.timeline._document import (
26
27
  DeleteTimelineDocument,
27
- GetTimelineDocument,
28
28
  PatchTimelineDocument,
29
29
  PostTimelineDocument,
30
30
  )
31
+ from glpi_python_client.models.api_schema.management._document import GetDocument
31
32
 
32
33
 
33
34
  class TimelineDocumentMixin(TransportMixin):
34
35
  """Synchronous CRUD helpers for the ticket document timeline endpoint."""
35
36
 
36
- def list_ticket_timeline_documents(
37
- self, ticket_id: GlpiId
38
- ) -> list[GetTimelineDocument]:
39
- """List all timeline documents linked to one ticket.
37
+ def list_ticket_timeline_documents(self, ticket_id: GlpiId) -> list[GetDocument]:
38
+ """List all documents linked to one ticket timeline.
40
39
 
41
40
  Parameters
42
41
  ----------
@@ -45,14 +44,16 @@ class TimelineDocumentMixin(TransportMixin):
45
44
 
46
45
  Returns
47
46
  -------
48
- list[GetTimelineDocument]
49
- Document links returned by the GLPI server, with the timeline
50
- envelope unwrapped where present.
47
+ list[GetDocument]
48
+ Document records returned by the GLPI server. The live API
49
+ wraps each entry in a ``{"type": "Document_Item", "item": {...}}``
50
+ envelope whose ``item`` value is a full ``Document`` record; the
51
+ envelope is unwrapped automatically.
51
52
  """
52
53
 
53
54
  return self._resource_list(
54
55
  f"{TICKET_ENDPOINT}/{ticket_id}/{TIMELINE_DOCUMENT_SUFFIX}",
55
- GetTimelineDocument,
56
+ GetDocument,
56
57
  failure_message=(
57
58
  f"Failed to list timeline documents for ticket {ticket_id}"
58
59
  ),
@@ -61,20 +62,20 @@ class TimelineDocumentMixin(TransportMixin):
61
62
 
62
63
  def get_ticket_timeline_document(
63
64
  self, ticket_id: GlpiId, document_link_id: GlpiId
64
- ) -> GetTimelineDocument:
65
- """Fetch one timeline document link by identifier.
65
+ ) -> GetDocument:
66
+ """Fetch one document linked to the ticket timeline by its document ID.
66
67
 
67
68
  Parameters
68
69
  ----------
69
70
  ticket_id : GlpiId
70
71
  Numeric identifier of the parent ticket.
71
72
  document_link_id : GlpiId
72
- Numeric identifier of the timeline document link to retrieve.
73
+ Numeric identifier of the linked document to retrieve.
73
74
 
74
75
  Returns
75
76
  -------
76
- GetTimelineDocument
77
- Validated document-link payload.
77
+ GetDocument
78
+ Validated document payload.
78
79
 
79
80
  Raises
80
81
  ------
@@ -85,7 +86,7 @@ class TimelineDocumentMixin(TransportMixin):
85
86
  return self._resource_get(
86
87
  f"{TICKET_ENDPOINT}/{ticket_id}/"
87
88
  f"{TIMELINE_DOCUMENT_SUFFIX}/{document_link_id}",
88
- GetTimelineDocument,
89
+ GetDocument,
89
90
  failure_message=(
90
91
  f"Failed to get timeline document {document_link_id} on "
91
92
  f"ticket {ticket_id}"
@@ -95,10 +95,12 @@ class StatisticsMixin(TransportMixin):
95
95
  Parameters
96
96
  ----------
97
97
  start_date : str | None, optional
98
- ISO ``YYYY-MM-DD`` start of the window. Defaults to
99
- ``end_date - default_days + 1`` when omitted.
98
+ ISO ``YYYY-MM-DD`` start of the window (inclusive from
99
+ 00:00:00). Defaults to ``end_date - default_days + 1``
100
+ when omitted.
100
101
  end_date : str | None, optional
101
- ISO ``YYYY-MM-DD`` end of the window. Defaults to today.
102
+ ISO ``YYYY-MM-DD`` end of the window (inclusive through
103
+ 23:59:59). Defaults to today.
102
104
  default_days : int, optional
103
105
  Span in days used when ``start_date`` is omitted (defaults
104
106
  to 30 and must be a positive integer).
@@ -147,9 +149,10 @@ class StatisticsMixin(TransportMixin):
147
149
  entity_filter = rsql_any_filter(
148
150
  *(f"entities_id=={e.id}" for e in entities if e.id is not None)
149
151
  )
150
-
152
+ date_filter = f"date_creation=ge={start.isoformat()};"
153
+ date_filter += f"date_creation=le={end.isoformat()} 23:59:59"
151
154
  query = rsql_all_filter(
152
- f"date_creation=ge={start.isoformat()};date_creation=le={end.isoformat()}",
155
+ date_filter,
153
156
  entity_filter,
154
157
  extra_filter,
155
158
  )
@@ -226,10 +229,12 @@ class StatisticsMixin(TransportMixin):
226
229
  Parameters
227
230
  ----------
228
231
  start_date : str | None, optional
229
- ISO ``YYYY-MM-DD`` start of the window. Defaults to
230
- ``end_date - default_days + 1`` when omitted.
232
+ ISO ``YYYY-MM-DD`` start of the window (inclusive from
233
+ 00:00:00). Defaults to ``end_date - default_days + 1``
234
+ when omitted.
231
235
  end_date : str | None, optional
232
- ISO ``YYYY-MM-DD`` end of the window. Defaults to today.
236
+ ISO ``YYYY-MM-DD`` end of the window (inclusive through
237
+ 23:59:59). Defaults to today.
233
238
  default_days : int, optional
234
239
  Span in days used when ``start_date`` is omitted (defaults
235
240
  to 30 and must be a positive integer).
@@ -269,9 +274,8 @@ class StatisticsMixin(TransportMixin):
269
274
  end_date=end_date,
270
275
  default_days=default_days,
271
276
  )
272
- date_filter = (
273
- f"date_creation=ge={start.isoformat()};date_creation=le={end.isoformat()}"
274
- )
277
+ date_filter = f"date_creation=ge={start.isoformat()};"
278
+ date_filter += f"date_creation=le={end.isoformat()} 23:59:59"
275
279
 
276
280
  entity_filter: str | None = None
277
281
  if entity_id is not None:
@@ -399,9 +403,11 @@ class StatisticsMixin(TransportMixin):
399
403
  firstname : str | None, optional
400
404
  Filter by given name (substring match).
401
405
  start_date : str | None, optional
402
- ISO ``YYYY-MM-DD`` start of the activity window.
406
+ ISO ``YYYY-MM-DD`` start of the activity window (inclusive
407
+ from 00:00:00).
403
408
  end_date : str | None, optional
404
- ISO ``YYYY-MM-DD`` end of the activity window. Defaults to today.
409
+ ISO ``YYYY-MM-DD`` end of the activity window (inclusive
410
+ through 23:59:59). Defaults to today.
405
411
  default_days : int, optional
406
412
  Span in days used when ``start_date`` is omitted (default 30).
407
413
 
@@ -460,9 +466,8 @@ class StatisticsMixin(TransportMixin):
460
466
  if u.id is not None
461
467
  }
462
468
 
463
- date_range = (
464
- f"date_creation=ge={start.isoformat()};date_creation=le={end.isoformat()}"
465
- )
469
+ date_range = f"date_creation=ge={start.isoformat()};"
470
+ date_range += f"date_creation=le={end.isoformat()} 23:59:59"
466
471
 
467
472
  users_output: dict[str, UserActivityEntry] = {}
468
473
  for uid in resolved_user_ids:
@@ -102,9 +102,11 @@ class AsyncStatisticsMixin(StatisticsMixin):
102
102
  Parameters
103
103
  ----------
104
104
  start_date : str | None, optional
105
- ISO ``YYYY-MM-DD`` start of the window.
105
+ ISO ``YYYY-MM-DD`` start of the window (inclusive from
106
+ 00:00:00).
106
107
  end_date : str | None, optional
107
- ISO ``YYYY-MM-DD`` end of the window.
108
+ ISO ``YYYY-MM-DD`` end of the window (inclusive through
109
+ 23:59:59). Defaults to today.
108
110
  default_days : int, optional
109
111
  Span in days used when ``start_date`` is omitted (default 30).
110
112
  entity_id : int | None, optional
@@ -143,10 +145,8 @@ class AsyncStatisticsMixin(StatisticsMixin):
143
145
  end_date=end_date,
144
146
  default_days=default_days,
145
147
  )
146
- date_filter = (
147
- f"date_creation=ge={start.isoformat()};date_creation=le={end.isoformat()}"
148
- )
149
-
148
+ date_filter = f"date_creation=ge={start.isoformat()};"
149
+ date_filter += f"date_creation=le={end.isoformat()} 23:59:59"
150
150
  entity_filter: str | None = None
151
151
  if entity_id is not None:
152
152
  entity_filter = f"entities_id=={entity_id}"
@@ -274,9 +274,11 @@ class AsyncStatisticsMixin(StatisticsMixin):
274
274
  Parameters
275
275
  ----------
276
276
  start_date : str | None, optional
277
- ISO ``YYYY-MM-DD`` start of the window.
277
+ ISO ``YYYY-MM-DD`` start of the window (inclusive from
278
+ 00:00:00).
278
279
  end_date : str | None, optional
279
- ISO ``YYYY-MM-DD`` end of the window. Defaults to today.
280
+ ISO ``YYYY-MM-DD`` end of the window (inclusive through
281
+ 23:59:59). Defaults to today.
280
282
  default_days : int, optional
281
283
  Span in days used when ``start_date`` is omitted (default 30).
282
284
  entity_id : int | None, optional
@@ -328,8 +330,10 @@ class AsyncStatisticsMixin(StatisticsMixin):
328
330
  *(f"entities_id=={e.id}" for e in entities if e.id is not None)
329
331
  )
330
332
 
333
+ date_filter = f"date_creation=ge={start.isoformat()};"
334
+ date_filter += f"date_creation=le={end.isoformat()} 23:59:59"
331
335
  query = rsql_all_filter(
332
- f"date_creation=ge={start.isoformat()};date_creation=le={end.isoformat()}",
336
+ date_filter,
333
337
  entity_filter,
334
338
  extra_filter,
335
339
  )
@@ -369,9 +373,11 @@ class AsyncStatisticsMixin(StatisticsMixin):
369
373
  firstname : str | None, optional
370
374
  Filter by given name (substring match).
371
375
  start_date : str | None, optional
372
- ISO ``YYYY-MM-DD`` start of the activity window.
376
+ ISO ``YYYY-MM-DD`` start of the activity window (inclusive
377
+ from 00:00:00).
373
378
  end_date : str | None, optional
374
- ISO ``YYYY-MM-DD`` end of the activity window. Defaults to today.
379
+ ISO ``YYYY-MM-DD`` end of the activity window (inclusive
380
+ through 23:59:59). Defaults to today.
375
381
  default_days : int, optional
376
382
  Span in days used when ``start_date`` is omitted (default 30).
377
383
 
@@ -438,9 +444,8 @@ class AsyncStatisticsMixin(StatisticsMixin):
438
444
  if u.id is not None
439
445
  }
440
446
 
441
- date_range = (
442
- f"date_creation=ge={start.isoformat()};date_creation=le={end.isoformat()}"
443
- )
447
+ date_range = f"date_creation=ge={start.isoformat()};"
448
+ date_range += f"date_creation=le={end.isoformat()} 23:59:59"
444
449
 
445
450
  users_output: dict[str, UserActivityEntry] = {}
446
451
  for uid in resolved_user_ids:
@@ -130,7 +130,7 @@ def test_get_ticket_statistics_default_window_uses_today(
130
130
  end = date.today()
131
131
  start = end - timedelta(days=6)
132
132
  assert f"date_creation=ge={start.isoformat()}" in captured["filter"]
133
- assert f"date_creation=le={end.isoformat()}" in captured["filter"]
133
+ assert f"date_creation=le={end.isoformat()} 23:59:59" in captured["filter"]
134
134
 
135
135
 
136
136
  def test_get_ticket_statistics_rejects_invalid_window(client: GlpiClient) -> None:
@@ -306,7 +306,7 @@ def test_get_ticket_statistics_default_days_window(client: GlpiClient) -> None:
306
306
  end = date.today()
307
307
  start = end - timedelta(days=13)
308
308
  assert f"date_creation=ge={start.isoformat()}" in captured["filter"]
309
- assert f"date_creation=le={end.isoformat()}" in captured["filter"]
309
+ assert f"date_creation=le={end.isoformat()} 23:59:59" in captured["filter"]
310
310
 
311
311
 
312
312
  # ---------------------------------------------------------------------------
@@ -570,7 +570,7 @@ def test_list_get_update_unlink_timeline_documents(client: GlpiClient) -> None:
570
570
 
571
571
  rec = _Recorder(
572
572
  get_payload=[
573
- {"type": "Document_Item", "item": {"id": 1, "documents_id": 99}},
573
+ {"type": "Document_Item", "item": {"id": 1, "filename": "report.txt"}},
574
574
  ]
575
575
  )
576
576
  rec.install(client)
@@ -578,7 +578,7 @@ def test_list_get_update_unlink_timeline_documents(client: GlpiClient) -> None:
578
578
  assert items[0].id == 1
579
579
  assert rec.calls[0]["endpoint"] == "Assistance/Ticket/7/Timeline/Document"
580
580
 
581
- rec._get_payload = {"id": 1, "documents_id": 99} # type: ignore[attr-defined]
581
+ rec._get_payload = {"id": 1, "filename": "report.txt"} # type: ignore[attr-defined]
582
582
  doc = client.get_ticket_timeline_document(7, 1)
583
583
  assert doc.id == 1
584
584
 
@@ -18,9 +18,6 @@ from pydantic import Field
18
18
  from glpi_python_client.models._base import GlpiModel
19
19
  from glpi_python_client.models.api_schema._common import IdNameRef
20
20
  from glpi_python_client.models.api_schema.assistance._ticket import GetTicket
21
- from glpi_python_client.models.api_schema.assistance.timeline._document import (
22
- GetTimelineDocument,
23
- )
24
21
  from glpi_python_client.models.api_schema.assistance.timeline._followup import (
25
22
  GetFollowup,
26
23
  )
@@ -30,6 +27,7 @@ from glpi_python_client.models.api_schema.assistance.timeline._solution import (
30
27
  from glpi_python_client.models.api_schema.assistance.timeline._task import (
31
28
  GetTicketTask,
32
29
  )
30
+ from glpi_python_client.models.api_schema.management._document import GetDocument
33
31
 
34
32
  _MAX_DATETIME = datetime.max
35
33
 
@@ -184,15 +182,15 @@ class GlpiTicketContext(GlpiModel):
184
182
  Linked followup records.
185
183
  solutions : list[GetSolution], optional
186
184
  Linked solution records.
187
- documents : list[GetTimelineDocument], optional
188
- Linked timeline document records.
185
+ documents : list[GetDocument], optional
186
+ Linked document records.
189
187
  """
190
188
 
191
189
  ticket: GetTicket
192
190
  tasks: list[GetTicketTask] = Field(default_factory=list)
193
191
  followups: list[GetFollowup] = Field(default_factory=list)
194
192
  solutions: list[GetSolution] = Field(default_factory=list)
195
- documents: list[GetTimelineDocument] = Field(default_factory=list)
193
+ documents: list[GetDocument] = Field(default_factory=list)
196
194
 
197
195
  def to_markdown(
198
196
  self,
@@ -331,9 +329,14 @@ class GlpiTicketContext(GlpiModel):
331
329
  lines.append("")
332
330
  lines.append("## Documents")
333
331
  for document in self.documents:
334
- identifier = document.documents_id or document.id
335
- label = document.filepath or (
336
- f"document #{identifier}" if identifier is not None else "document"
332
+ label = (
333
+ document.filename
334
+ or document.name
335
+ or (
336
+ f"document #{document.id}"
337
+ if document.id is not None
338
+ else "document"
339
+ )
337
340
  )
338
341
  lines.append(f"- {label}")
339
342
 
@@ -38,11 +38,11 @@ def test_ticket_context_accepts_timeline_records() -> None:
38
38
  "tasks": [{"id": 11, "content": "<p>do</p>"}],
39
39
  "followups": [{"id": 12, "content": "<p>note</p>"}],
40
40
  "solutions": [{"id": 13, "content": "<p>fix</p>"}],
41
- "documents": [{"id": 14, "documents_id": 99}],
41
+ "documents": [{"id": 14, "filename": "report.pdf"}],
42
42
  }
43
43
  context = GlpiTicketContext.model_validate(payload)
44
44
  assert context.tasks[0].id == 11
45
- assert context.documents[0].documents_id == 99
45
+ assert context.documents[0].filename == "report.pdf"
46
46
 
47
47
 
48
48
  def test_to_markdown_renders_header_and_status() -> None:
@@ -151,8 +151,8 @@ def test_to_markdown_renders_solution_and_documents() -> None:
151
151
  "ticket": {"id": 7, "name": "Reset"},
152
152
  "solutions": [{"id": 4, "content": "All fixed"}],
153
153
  "documents": [
154
- {"id": 11, "documents_id": 99, "filepath": "logs/run.txt"},
155
- {"id": 12, "documents_id": 100},
154
+ {"id": 11, "filename": "run.txt"},
155
+ {"id": 12},
156
156
  ],
157
157
  }
158
158
  )
@@ -160,8 +160,8 @@ def test_to_markdown_renders_solution_and_documents() -> None:
160
160
  assert "### Solution #4" in rendered
161
161
  assert "All fixed" in rendered
162
162
  assert "## Documents" in rendered
163
- assert "- logs/run.txt" in rendered
164
- assert "- document #100" in rendered
163
+ assert "- run.txt" in rendered
164
+ assert "- document #12" in rendered
165
165
 
166
166
 
167
167
  def test_to_markdown_handles_empty_timeline() -> None:
@@ -232,7 +232,7 @@ _FULL_PAYLOAD = {
232
232
  "followups": [{"id": 10, "content": "followup body"}],
233
233
  "tasks": [{"id": 20, "content": "task body", "duration": 600}],
234
234
  "solutions": [{"id": 30, "content": "solution body"}],
235
- "documents": [{"id": 40, "documents_id": 99, "filepath": "file.txt"}],
235
+ "documents": [{"id": 40, "filename": "file.txt"}],
236
236
  }
237
237
 
238
238
 
@@ -25,7 +25,7 @@ exclude = [
25
25
 
26
26
  [project]
27
27
  name = "glpi-python-client"
28
- version = "0.3.2"
28
+ version = "0.3.3"
29
29
  description = "A typed Python client for GLPI ITSM APIs."
30
30
  readme = "README.md"
31
31
  requires-python = ">=3.10"
@@ -1,84 +0,0 @@
1
- Development
2
- ===========
3
-
4
- Local Setup
5
- -----------
6
-
7
- Create a virtual environment and install the development dependencies:
8
-
9
- .. code-block:: console
10
-
11
- python -m venv .venv
12
- .venv\Scripts\activate
13
- python -m pip install --upgrade pip
14
- python -m pip install -e .[dev]
15
- python -m pre_commit install
16
-
17
- The repository ships a root ``.pre-commit-config.yaml`` that runs Ruff on each
18
- commit. The lint hook applies safe fixes first, then Ruff formats the touched
19
- files.
20
-
21
- Quality Checks
22
- --------------
23
-
24
- Run the focused checks before opening a pull request:
25
-
26
- .. code-block:: console
27
-
28
- python -m pre_commit run --all-files
29
- python -m pytest
30
- python -m ruff check .
31
- python -m mypy glpi_python_client
32
- python -m sphinx -b html docs docs/_build/html
33
-
34
- Package Layout
35
- --------------
36
-
37
- ``glpi_python_client.__init__``
38
- Public import surface (``GlpiClient``, ``GlpiTicketContext``, public Pydantic
39
- models, enums, and ``__version__``).
40
-
41
- ``glpi_python_client.clients.glpi_client``
42
- Composition root. ``GlpiClient`` mixes the per-resource async API mixins,
43
- the OAuth2 token manager, the asynchronous v2 transport, and the optional
44
- internal v1 session used for document uploads.
45
-
46
- ``glpi_python_client.clients.api``
47
- Async API mixins generated from the GLPI v2 OpenAPI contract: tickets,
48
- ticket timeline (followups, tasks, solutions, documents), team members,
49
- documents, users, locations, entities, ...
50
-
51
- ``glpi_python_client.clients.custom``
52
- Higher-level helpers built on top of the contract mixins:
53
- ``get_ticket_context``, ``get_ticket_statistics``, ``get_task_statistics``.
54
-
55
- ``glpi_python_client.clients.commons``
56
- Shared HTTP transport pieces, including the timeline envelope unwrap that
57
- reconciles live server behaviour with the OpenAPI contract.
58
-
59
- ``glpi_python_client.models.api_schema``
60
- Contract-aligned Pydantic v2 models (``Get``/``Post``/``Patch``/``Delete``)
61
- for each GLPI v2 resource.
62
-
63
- ``glpi_python_client.models.custom_schema``
64
- Composite models such as ``GlpiTicketContext`` returned by the custom
65
- helpers.
66
-
67
- Adding Endpoints
68
- ----------------
69
-
70
- #. Add or extend the contract-aligned models in
71
- ``glpi_python_client.models.api_schema``.
72
- #. Add the async mixin and method under ``glpi_python_client.clients.api``,
73
- mirroring the OpenAPI path and HTTP verb.
74
- #. When the live server diverges from the contract, document the choice in the
75
- module docstring and (when needed) wire an unwrap helper from
76
- ``glpi_python_client.clients.commons``.
77
- #. Re-export new public symbols from ``glpi_python_client.__init__``.
78
- #. Add tests for payload serialization, response parsing, and client behaviour.
79
- #. Document the workflow in :doc:`user_guide` and the matching skill in
80
- ``skills/``.
81
-
82
- Keep organization-specific entity, profile, and category defaults outside the
83
- library core. Applications can apply their own mapping before calling the
84
- client.