glpi-python-client 0.3.3__tar.gz → 0.3.4__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.
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/PKG-INFO +1 -1
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/api_reference.rst +30 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/user_guide.rst +59 -7
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/__init__.py +9 -1
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/auth/_v1_session.py +173 -49
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/auth/tests/test_v1_session.py +184 -21
- glpi_python_client-0.3.3/glpi_python_client/clients/sync_client.py → glpi_python_client-0.3.4/glpi_python_client/clients/_base_client.py +21 -106
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/__init__.py +2 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/management/_document.py +1 -7
- glpi_python_client-0.3.4/glpi_python_client/clients/api/plugins/__init__.py +10 -0
- glpi_python_client-0.3.4/glpi_python_client/clients/api/plugins/_fields.py +411 -0
- glpi_python_client-0.3.4/glpi_python_client/clients/api/plugins/tests/test_fields_mixin.py +377 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/async_client.py +14 -90
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/_config.py +1 -1
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/_http.py +14 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/_transport.py +29 -0
- glpi_python_client-0.3.4/glpi_python_client/clients/sync_client.py +129 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/__init__.py +10 -0
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/plugins/__init__.py +19 -0
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/plugins/_fields.py +177 -0
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/plugins/tests/__init__.py +0 -0
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/plugins/tests/test_fields_schemas.py +82 -0
- glpi_python_client-0.3.4/glpi_python_client/py.typed +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/testing/utils.py +2 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/pyproject.toml +1 -1
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/.gitignore +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/.pre-commit-config.yaml +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/.readthedocs.yaml +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/CONTRIBUTING.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/LICENSE +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/README.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/_static/.gitkeep +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/conf.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/development.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/development_rtd.rst +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/glpi_api_contract.json +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/index.rst +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/installation.rst +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/publishing.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/publishing_rtd.rst +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/docs/sponsoring.rst +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/auth/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/auth/auth.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/auth/tests/test_auth.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/administration/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/administration/_entity.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/administration/_user.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/assistance/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/assistance/_team.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/assistance/_ticket.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/dropdowns/_location.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/api/management/__init__.py +0 -0
- /glpi_python_client-0.3.3/glpi_python_client/py.typed → /glpi_python_client-0.3.4/glpi_python_client/clients/api/plugins/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/_async_bridge.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/_constants.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/_errors.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/_filters.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/_payloads.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/tests/test_errors.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/tests/test_filters.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/tests/test_http.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/tests/test_payloads.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/commons/tests/test_transport.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/_pagination_async.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/_statistics.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/_statistics_async.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/_ticket_context.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/_ticket_context_async.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/tests/test_statistics.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/tests/test_statistics_async.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/custom/tests/test_ticket_context.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/tests/test_api_coverage.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/tests/test_async_branches.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/tests/test_async_smoke.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/tests/test_glpi_client.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/tests/test_parity.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/clients/tests/test_smoke.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/content/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/content/conversion.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/content/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/content/tests/test_conversion.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/_base.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/_common.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/_content.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/administration/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/administration/_entity.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/administration/_user.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/administration/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/administration/tests/test_administration_schemas.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/_team.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/_ticket.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/tests/test_assistance_schemas.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/tests/test_content_roundtrip.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/timeline/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/assistance/timeline/tests/test_timeline_schemas.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/dropdowns/_location.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/dropdowns/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/dropdowns/tests/test_dropdowns_schemas.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/enums.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/management/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/management/_document.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/management/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/api_schema/management/tests/test_management_schemas.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/custom_schema/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/custom_schema/_ticket_context.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/custom_schema/tests/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/models/custom_schema/tests/test_ticket_context.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/testing/__init__.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/testing/fixtures.py +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/skills/README.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/skills/glpi-client-setup/SKILL.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/skills/glpi-document-workflow/SKILL.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/skills/glpi-reporting-and-context/SKILL.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/skills/glpi-team-members/SKILL.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/skills/glpi-ticket-timeline/SKILL.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/skills/glpi-ticket-workflow/SKILL.md +0 -0
- {glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/skills/glpi-user-location-provisioning/SKILL.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glpi-python-client
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.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/
|
|
@@ -289,6 +289,36 @@ Entities
|
|
|
289
289
|
:undoc-members:
|
|
290
290
|
:show-inheritance:
|
|
291
291
|
|
|
292
|
+
Plugin: Fields (custom fields)
|
|
293
|
+
------------------------------
|
|
294
|
+
|
|
295
|
+
Schemas returned by the GLPI ``Fields`` plugin (legacy v1 REST endpoints).
|
|
296
|
+
The companion mixin methods are exposed on :class:`GlpiClient` /
|
|
297
|
+
:class:`AsyncGlpiClient` as ``list_plugin_fields_containers``,
|
|
298
|
+
``list_plugin_fields_fields``, ``list_item_plugin_field_rows``,
|
|
299
|
+
``create_item_plugin_field_row``, ``update_item_plugin_field_row``,
|
|
300
|
+
``get_ticket_custom_fields`` and ``set_ticket_custom_fields``.
|
|
301
|
+
|
|
302
|
+
.. autoclass:: GetPluginFieldsContainer
|
|
303
|
+
:members:
|
|
304
|
+
:undoc-members:
|
|
305
|
+
:show-inheritance:
|
|
306
|
+
|
|
307
|
+
.. autoclass:: GetPluginFieldsField
|
|
308
|
+
:members:
|
|
309
|
+
:undoc-members:
|
|
310
|
+
:show-inheritance:
|
|
311
|
+
|
|
312
|
+
.. autoclass:: GetPluginFieldsValueRow
|
|
313
|
+
:members:
|
|
314
|
+
:undoc-members:
|
|
315
|
+
:show-inheritance:
|
|
316
|
+
|
|
317
|
+
.. autoclass:: PostPluginFieldsValueRow
|
|
318
|
+
:members:
|
|
319
|
+
:undoc-members:
|
|
320
|
+
:show-inheritance:
|
|
321
|
+
|
|
292
322
|
Enums
|
|
293
323
|
-----
|
|
294
324
|
|
|
@@ -11,8 +11,10 @@ whose surface is built from contract-aligned per-endpoint mixins:
|
|
|
11
11
|
it to a worker thread via :func:`asyncio.to_thread`.
|
|
12
12
|
|
|
13
13
|
Both clients speak the GLPI **v2** high-level API and fall back to the
|
|
14
|
-
legacy v1
|
|
15
|
-
|
|
14
|
+
legacy v1 API for features that are not exposed by v2, currently
|
|
15
|
+
binary document uploads and the ``Fields`` plugin custom-field
|
|
16
|
+
helpers. They expose the exact same endpoint methods and accept the
|
|
17
|
+
same constructor arguments.
|
|
16
18
|
Public methods always return Pydantic models (or simple Python types)
|
|
17
19
|
and never raw dictionaries.
|
|
18
20
|
|
|
@@ -36,7 +38,8 @@ The guide is split into the following sections:
|
|
|
36
38
|
one-to-one to GLPI v2 endpoints (tickets, timeline, team members,
|
|
37
39
|
users, locations, entities, documents).
|
|
38
40
|
5. **Added functionalities** — helpers built on top of the API mixins:
|
|
39
|
-
|
|
41
|
+
the ``Fields`` plugin custom-field helpers, the aggregated ticket
|
|
42
|
+
context view, and the reporting helpers.
|
|
40
43
|
6. **End-to-end examples** — full workflows that combine the previous
|
|
41
44
|
building blocks.
|
|
42
45
|
|
|
@@ -111,7 +114,9 @@ Optional constructor arguments
|
|
|
111
114
|
* ``auth_token_refresh`` — number of seconds before token expiry at
|
|
112
115
|
which the auth manager proactively refreshes the OAuth access token.
|
|
113
116
|
* ``v1_base_url`` and ``v1_user_token`` — together enable the legacy v1
|
|
114
|
-
|
|
117
|
+
fallback used by :meth:`GlpiClient.upload_document` and the
|
|
118
|
+
``Fields`` plugin helpers such as
|
|
119
|
+
:meth:`GlpiClient.get_ticket_custom_fields`.
|
|
115
120
|
* ``executor`` (:class:`AsyncGlpiClient` only) — an explicit
|
|
116
121
|
:class:`concurrent.futures.Executor` used to dispatch the wrapped
|
|
117
122
|
synchronous calls. Defaults to the standard library thread pool
|
|
@@ -616,6 +621,55 @@ Example output::
|
|
|
616
621
|
The helpers in this section are not part of the GLPI contract. They are
|
|
617
622
|
small utilities the client builds on top of the API mixins.
|
|
618
623
|
|
|
624
|
+
Ticket custom fields via the Fields plugin
|
|
625
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
626
|
+
|
|
627
|
+
The `Fields plugin <https://github.com/pluginsGLPI/fields>`_ exposes
|
|
628
|
+
ticket custom fields through the legacy v1 API rather than the GLPI v2
|
|
629
|
+
contract. Configure the client with ``v1_base_url`` and
|
|
630
|
+
``v1_user_token`` (or the matching ``GLPI_V1_*`` environment
|
|
631
|
+
variables), then use the discovery helpers when you need the plugin's
|
|
632
|
+
internal container and field names:
|
|
633
|
+
|
|
634
|
+
.. code-block:: python
|
|
635
|
+
|
|
636
|
+
from glpi_python_client import GlpiClient
|
|
637
|
+
|
|
638
|
+
with GlpiClient(
|
|
639
|
+
glpi_api_url="https://glpi.example.com/api.php/v2",
|
|
640
|
+
client_id="oauth-client-id",
|
|
641
|
+
client_secret="oauth-client-secret",
|
|
642
|
+
username="api-user",
|
|
643
|
+
password="api-password",
|
|
644
|
+
v1_base_url="https://glpi.example.com/apirest.php",
|
|
645
|
+
v1_user_token="legacy-user-token",
|
|
646
|
+
) as client:
|
|
647
|
+
containers = client.list_plugin_fields_containers(itemtype="Ticket")
|
|
648
|
+
for container in containers:
|
|
649
|
+
print(container.id, container.name)
|
|
650
|
+
fields = client.list_plugin_fields_fields(container_id=container.id)
|
|
651
|
+
print([field.name for field in fields])
|
|
652
|
+
|
|
653
|
+
custom_fields = client.get_ticket_custom_fields(ticket_id)
|
|
654
|
+
print(custom_fields)
|
|
655
|
+
|
|
656
|
+
client.set_ticket_custom_fields(
|
|
657
|
+
ticket_id,
|
|
658
|
+
{
|
|
659
|
+
"aidelarsolution": {
|
|
660
|
+
"aidelarsolutionfield": "<p>Handled by the NOC shift</p>",
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
The high-level ``get_ticket_custom_fields`` /
|
|
666
|
+
``set_ticket_custom_fields`` pair uses the mapping
|
|
667
|
+
``{container_name: {field_name: value}}`` and automatically decides
|
|
668
|
+
whether the v1 plugin needs a row creation or an in-place update. Drop
|
|
669
|
+
to ``list_item_plugin_field_rows``, ``create_item_plugin_field_row``,
|
|
670
|
+
or ``update_item_plugin_field_row`` only when you need the raw v1 row
|
|
671
|
+
shape.
|
|
672
|
+
|
|
619
673
|
Aggregated ticket context
|
|
620
674
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
621
675
|
|
|
@@ -780,9 +834,7 @@ batches until the API returns fewer rows than the requested
|
|
|
780
834
|
|
|
781
835
|
Always pass an RSQL filter to ``iter_search_tickets``. Querying
|
|
782
836
|
without any filter can return very large result sets and may cause
|
|
783
|
-
the GLPI server to return a 500
|
|
784
|
-
two generators (``iter_search_users``, ``iter_search_entities``) are
|
|
785
|
-
not affected because those collections are typically much smaller.
|
|
837
|
+
the GLPI server to return a 500 errors.
|
|
786
838
|
|
|
787
839
|
On the asynchronous client the same helpers are exposed as **async
|
|
788
840
|
generators** through the bridge, so each ``next()`` call runs off the
|
|
@@ -30,6 +30,9 @@ from glpi_python_client.models import (
|
|
|
30
30
|
GetEntity,
|
|
31
31
|
GetFollowup,
|
|
32
32
|
GetLocation,
|
|
33
|
+
GetPluginFieldsContainer,
|
|
34
|
+
GetPluginFieldsField,
|
|
35
|
+
GetPluginFieldsValueRow,
|
|
33
36
|
GetSolution,
|
|
34
37
|
GetTeamMember,
|
|
35
38
|
GetTicket,
|
|
@@ -63,6 +66,7 @@ from glpi_python_client.models import (
|
|
|
63
66
|
PostEntity,
|
|
64
67
|
PostFollowup,
|
|
65
68
|
PostLocation,
|
|
69
|
+
PostPluginFieldsValueRow,
|
|
66
70
|
PostSolution,
|
|
67
71
|
PostTeamMember,
|
|
68
72
|
PostTicket,
|
|
@@ -72,7 +76,7 @@ from glpi_python_client.models import (
|
|
|
72
76
|
TicketMarkdownOptions,
|
|
73
77
|
)
|
|
74
78
|
|
|
75
|
-
__version__ = "0.3.
|
|
79
|
+
__version__ = "0.3.4"
|
|
76
80
|
|
|
77
81
|
__all__ = [
|
|
78
82
|
"AsyncGlpiClient",
|
|
@@ -90,6 +94,9 @@ __all__ = [
|
|
|
90
94
|
"GetEntity",
|
|
91
95
|
"GetFollowup",
|
|
92
96
|
"GetLocation",
|
|
97
|
+
"GetPluginFieldsContainer",
|
|
98
|
+
"GetPluginFieldsField",
|
|
99
|
+
"GetPluginFieldsValueRow",
|
|
93
100
|
"GetSolution",
|
|
94
101
|
"GetTeamMember",
|
|
95
102
|
"GetTicket",
|
|
@@ -124,6 +131,7 @@ __all__ = [
|
|
|
124
131
|
"PostEntity",
|
|
125
132
|
"PostFollowup",
|
|
126
133
|
"PostLocation",
|
|
134
|
+
"PostPluginFieldsValueRow",
|
|
127
135
|
"PostSolution",
|
|
128
136
|
"PostTeamMember",
|
|
129
137
|
"PostTicket",
|
{glpi_python_client-0.3.3 → glpi_python_client-0.3.4}/glpi_python_client/auth/_v1_session.py
RENAMED
|
@@ -1,10 +1,27 @@
|
|
|
1
|
-
"""GLPI v1 REST session used
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
the
|
|
6
|
-
``
|
|
7
|
-
|
|
1
|
+
"""GLPI v1 REST session used for legacy endpoints not exposed by v2.
|
|
2
|
+
|
|
3
|
+
Two consumers currently share this session:
|
|
4
|
+
|
|
5
|
+
* the management :class:`DocumentMixin` for the multipart
|
|
6
|
+
``POST /Document`` upload (the v2 API does not advertise a binary
|
|
7
|
+
upload route), and
|
|
8
|
+
* the :class:`PluginFieldsMixin` for the GLPI "Fields" plugin endpoints
|
|
9
|
+
(``PluginFieldsContainer``, ``PluginFieldsField`` and the per-item
|
|
10
|
+
value itemtypes), which the v2 contract does not surface at all.
|
|
11
|
+
|
|
12
|
+
The session wrapper owns the authenticated v1 lifecycle (init, refresh,
|
|
13
|
+
kill) and exposes the typed ``upload_document`` helper plus the generic
|
|
14
|
+
``request_json`` JSON-only HTTP helper that newer mixins build on.
|
|
15
|
+
|
|
16
|
+
Retry policy
|
|
17
|
+
------------
|
|
18
|
+
Every public dispatch helper (``_init_session``, ``request_json``,
|
|
19
|
+
``upload_document``) carries the same :mod:`tenacity` retry decorator
|
|
20
|
+
used by the v2 transport: three attempts spaced by three seconds,
|
|
21
|
+
triggered exclusively by :class:`requests.RequestException` (which
|
|
22
|
+
:func:`finalize_request_response` raises for 5xx server errors).
|
|
23
|
+
:class:`ValueError` raised by status-code or payload checks does not
|
|
24
|
+
trigger a retry — client-side or 4xx failures are surfaced immediately.
|
|
8
25
|
"""
|
|
9
26
|
|
|
10
27
|
from __future__ import annotations
|
|
@@ -12,15 +29,26 @@ from __future__ import annotations
|
|
|
12
29
|
import json
|
|
13
30
|
import logging
|
|
14
31
|
from datetime import datetime, timedelta, timezone
|
|
15
|
-
from typing import cast
|
|
32
|
+
from typing import Any, cast
|
|
16
33
|
|
|
17
34
|
import requests
|
|
18
|
-
from tenacity import retry, stop_after_attempt, wait_fixed
|
|
35
|
+
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
|
|
36
|
+
|
|
37
|
+
from glpi_python_client.clients.commons._http import (
|
|
38
|
+
ensure_response_status,
|
|
39
|
+
finalize_request_response,
|
|
40
|
+
response_json_or_empty,
|
|
41
|
+
)
|
|
19
42
|
|
|
20
43
|
logger = logging.getLogger(__name__)
|
|
21
44
|
|
|
22
45
|
_DEFAULT_SESSION_REFRESH_INTERVAL_SECONDS = 15 * 60
|
|
23
46
|
_AUTH_FAILURE_STATUS_CODES = frozenset({401, 403})
|
|
47
|
+
_RETRY_ON_NETWORK_ERRORS = retry(
|
|
48
|
+
retry=retry_if_exception_type(requests.RequestException),
|
|
49
|
+
stop=stop_after_attempt(3),
|
|
50
|
+
wait=wait_fixed(3),
|
|
51
|
+
)
|
|
24
52
|
|
|
25
53
|
|
|
26
54
|
class GLPIV1Session:
|
|
@@ -36,7 +64,7 @@ class GLPIV1Session:
|
|
|
36
64
|
*,
|
|
37
65
|
base_url: str,
|
|
38
66
|
user_token: str,
|
|
39
|
-
app_token: str,
|
|
67
|
+
app_token: str | None = None,
|
|
40
68
|
verify_ssl: bool = True,
|
|
41
69
|
session_refresh_interval_seconds: int = (
|
|
42
70
|
_DEFAULT_SESSION_REFRESH_INTERVAL_SECONDS
|
|
@@ -59,12 +87,14 @@ class GLPIV1Session:
|
|
|
59
87
|
self._session_token: str | None = None
|
|
60
88
|
self._session_started_at: datetime | None = None
|
|
61
89
|
|
|
62
|
-
@
|
|
90
|
+
@_RETRY_ON_NETWORK_ERRORS
|
|
63
91
|
def _init_session(self) -> None:
|
|
64
92
|
"""Acquire one fresh GLPI v1 session token via ``GET /initSession``.
|
|
65
93
|
|
|
66
94
|
The call replaces any existing session state and stores the
|
|
67
95
|
authentication timestamp used by the refresh-interval check.
|
|
96
|
+
Network errors and 5xx responses are retried; 4xx and payload
|
|
97
|
+
errors propagate immediately as :class:`ValueError`.
|
|
68
98
|
"""
|
|
69
99
|
|
|
70
100
|
headers: dict[str, str] = {
|
|
@@ -75,16 +105,20 @@ class GLPIV1Session:
|
|
|
75
105
|
if self._app_token:
|
|
76
106
|
headers["App-Token"] = self._app_token
|
|
77
107
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
108
|
+
url = f"{self._base_url}/initSession"
|
|
109
|
+
response = self._http.get(url, headers=headers, timeout=30)
|
|
110
|
+
finalize_request_response(
|
|
111
|
+
response,
|
|
112
|
+
method="get",
|
|
113
|
+
url=url,
|
|
114
|
+
success_statuses=(200,),
|
|
115
|
+
logger=logger,
|
|
116
|
+
)
|
|
117
|
+
ensure_response_status(
|
|
118
|
+
response,
|
|
119
|
+
success_statuses=(200,),
|
|
120
|
+
failure_message="GLPI v1 initSession failed",
|
|
82
121
|
)
|
|
83
|
-
if response.status_code != 200:
|
|
84
|
-
raise ValueError(
|
|
85
|
-
"GLPI v1 initSession failed: "
|
|
86
|
-
f"{response.status_code} {response.text[:300]}"
|
|
87
|
-
)
|
|
88
122
|
|
|
89
123
|
token = response.json().get("session_token")
|
|
90
124
|
if not token:
|
|
@@ -140,11 +174,12 @@ class GLPIV1Session:
|
|
|
140
174
|
"""Drop the current GLPI v1 session token and acquire a new one.
|
|
141
175
|
|
|
142
176
|
The previous token is best-effort killed so the GLPI server can release
|
|
143
|
-
the associated session state immediately.
|
|
177
|
+
the associated session state immediately. ``_init_session`` will set
|
|
178
|
+
the new token on success or raise, leaving the existing state
|
|
179
|
+
untouched on failure (the retry decorator handles transients).
|
|
144
180
|
"""
|
|
145
181
|
|
|
146
|
-
|
|
147
|
-
if old_token is not None:
|
|
182
|
+
if self._session_token is not None:
|
|
148
183
|
try:
|
|
149
184
|
self._http.get(
|
|
150
185
|
f"{self._base_url}/killSession",
|
|
@@ -153,8 +188,6 @@ class GLPIV1Session:
|
|
|
153
188
|
)
|
|
154
189
|
except Exception:
|
|
155
190
|
logger.warning("Failed to kill stale GLPI v1 session.", exc_info=True)
|
|
156
|
-
self._session_token = None
|
|
157
|
-
self._session_started_at = None
|
|
158
191
|
self._init_session()
|
|
159
192
|
|
|
160
193
|
def _headers(self) -> dict[str, str]:
|
|
@@ -172,13 +205,19 @@ class GLPIV1Session:
|
|
|
172
205
|
method: str,
|
|
173
206
|
url: str,
|
|
174
207
|
*,
|
|
208
|
+
success_statuses: tuple[int, ...],
|
|
175
209
|
headers: dict[str, str] | None = None,
|
|
176
|
-
**kwargs:
|
|
210
|
+
**kwargs: Any,
|
|
177
211
|
) -> requests.Response:
|
|
178
|
-
"""Send one authenticated GLPI v1 request
|
|
179
|
-
|
|
180
|
-
When the GLPI server rejects the current token
|
|
181
|
-
session and retries the request once
|
|
212
|
+
"""Send one authenticated GLPI v1 request and finalize the response.
|
|
213
|
+
|
|
214
|
+
When the GLPI server rejects the current token the helper renews
|
|
215
|
+
the session and retries the request once. The returned response
|
|
216
|
+
has already been passed through :func:`finalize_request_response`
|
|
217
|
+
so 5xx errors surface as :class:`requests.HTTPError` for the
|
|
218
|
+
outer tenacity retry to catch; non-success statuses outside the
|
|
219
|
+
``success_statuses`` set are logged but otherwise returned for
|
|
220
|
+
the caller to validate with :func:`ensure_response_status`.
|
|
182
221
|
"""
|
|
183
222
|
|
|
184
223
|
request_headers = {**self._headers(), **(headers or {})}
|
|
@@ -187,18 +226,23 @@ class GLPIV1Session:
|
|
|
187
226
|
requests.Response,
|
|
188
227
|
request_method(url, headers=request_headers, **kwargs),
|
|
189
228
|
)
|
|
190
|
-
if
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
229
|
+
if _is_auth_failure_response(response):
|
|
230
|
+
logger.warning(
|
|
231
|
+
"GLPI v1 session token was rejected; refreshing session and "
|
|
232
|
+
"retrying request once."
|
|
233
|
+
)
|
|
234
|
+
self._renew_session()
|
|
235
|
+
request_headers = {**self._headers(), **(headers or {})}
|
|
236
|
+
response = cast(
|
|
237
|
+
requests.Response,
|
|
238
|
+
request_method(url, headers=request_headers, **kwargs),
|
|
239
|
+
)
|
|
240
|
+
return finalize_request_response(
|
|
241
|
+
response,
|
|
242
|
+
method=method,
|
|
243
|
+
url=url,
|
|
244
|
+
success_statuses=success_statuses,
|
|
245
|
+
logger=logger,
|
|
202
246
|
)
|
|
203
247
|
|
|
204
248
|
def close(self) -> None:
|
|
@@ -223,7 +267,84 @@ class GLPIV1Session:
|
|
|
223
267
|
self._session_started_at = None
|
|
224
268
|
self._http.close()
|
|
225
269
|
|
|
226
|
-
@
|
|
270
|
+
@_RETRY_ON_NETWORK_ERRORS
|
|
271
|
+
def request_json(
|
|
272
|
+
self,
|
|
273
|
+
method: str,
|
|
274
|
+
path: str,
|
|
275
|
+
*,
|
|
276
|
+
params: dict[str, object] | None = None,
|
|
277
|
+
json_body: dict[str, object] | None = None,
|
|
278
|
+
success_statuses: tuple[int, ...] = (200, 201, 204, 206),
|
|
279
|
+
failure_message: str | None = None,
|
|
280
|
+
) -> object:
|
|
281
|
+
"""Send one JSON-only authenticated request to the GLPI v1 API.
|
|
282
|
+
|
|
283
|
+
The helper centralises session-token handling, the one-shot retry
|
|
284
|
+
on token rejection, status validation and JSON parsing so callers
|
|
285
|
+
can stay focused on their endpoint semantics. Network errors and
|
|
286
|
+
5xx responses are retried; 4xx and payload errors propagate
|
|
287
|
+
immediately as :class:`ValueError`.
|
|
288
|
+
|
|
289
|
+
Parameters
|
|
290
|
+
----------
|
|
291
|
+
method : str
|
|
292
|
+
HTTP verb (``"GET"``, ``"POST"``, ``"PUT"``, ``"DELETE"``).
|
|
293
|
+
path : str
|
|
294
|
+
Resource path appended to the v1 base URL (without leading
|
|
295
|
+
slash, e.g. ``"PluginFieldsContainer"``).
|
|
296
|
+
params : dict[str, object] | None, optional
|
|
297
|
+
Query-string parameters forwarded to ``requests``.
|
|
298
|
+
json_body : dict[str, object] | None, optional
|
|
299
|
+
JSON body serialised into the request when set. The
|
|
300
|
+
``Content-Type: application/json`` header is added
|
|
301
|
+
automatically.
|
|
302
|
+
success_statuses : tuple[int, ...], optional
|
|
303
|
+
HTTP status codes considered successful (default covers the
|
|
304
|
+
CRUD codes returned by the v1 API).
|
|
305
|
+
failure_message : str | None, optional
|
|
306
|
+
Prefix used in the :class:`ValueError` raised on a
|
|
307
|
+
non-success status. Defaults to ``"GLPI v1 {METHOD} {path}
|
|
308
|
+
failed"``.
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
object
|
|
313
|
+
Parsed JSON body for non-empty responses; an empty ``dict``
|
|
314
|
+
when the body is empty or contains only whitespace.
|
|
315
|
+
|
|
316
|
+
Raises
|
|
317
|
+
------
|
|
318
|
+
ValueError
|
|
319
|
+
If the v1 server returns a non-success HTTP status outside
|
|
320
|
+
the 5xx range (which surfaces as :class:`requests.HTTPError`
|
|
321
|
+
and is retried).
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
url = f"{self._base_url}/{path.lstrip('/')}"
|
|
325
|
+
kwargs: dict[str, Any] = {"timeout": 30}
|
|
326
|
+
if params is not None:
|
|
327
|
+
kwargs["params"] = params
|
|
328
|
+
headers: dict[str, str] = {}
|
|
329
|
+
if json_body is not None:
|
|
330
|
+
kwargs["data"] = json.dumps(json_body)
|
|
331
|
+
headers["Content-Type"] = "application/json"
|
|
332
|
+
response = self._authenticated_request(
|
|
333
|
+
method,
|
|
334
|
+
url,
|
|
335
|
+
success_statuses=success_statuses,
|
|
336
|
+
headers=headers or None,
|
|
337
|
+
**kwargs,
|
|
338
|
+
)
|
|
339
|
+
ensure_response_status(
|
|
340
|
+
response,
|
|
341
|
+
success_statuses=success_statuses,
|
|
342
|
+
failure_message=failure_message
|
|
343
|
+
or f"GLPI v1 {method.upper()} {path} failed",
|
|
344
|
+
)
|
|
345
|
+
return response_json_or_empty(response)
|
|
346
|
+
|
|
347
|
+
@_RETRY_ON_NETWORK_ERRORS
|
|
227
348
|
def upload_document(
|
|
228
349
|
self,
|
|
229
350
|
filename: str,
|
|
@@ -238,7 +359,9 @@ class GLPIV1Session:
|
|
|
238
359
|
|
|
239
360
|
The legacy v1 endpoint uses a multipart upload manifest so the GLPI
|
|
240
361
|
server can create the document, link it to the optional parent ticket,
|
|
241
|
-
and assign it to the provided entity in a single round-trip.
|
|
362
|
+
and assign it to the provided entity in a single round-trip. Network
|
|
363
|
+
errors and 5xx responses are retried; 4xx and payload errors
|
|
364
|
+
propagate immediately as :class:`ValueError`.
|
|
242
365
|
"""
|
|
243
366
|
|
|
244
367
|
manifest_input: dict[str, object] = {
|
|
@@ -255,17 +378,18 @@ class GLPIV1Session:
|
|
|
255
378
|
response = self._authenticated_request(
|
|
256
379
|
"POST",
|
|
257
380
|
f"{self._base_url}/Document",
|
|
381
|
+
success_statuses=(200, 201),
|
|
258
382
|
files=[
|
|
259
383
|
("uploadManifest", (None, manifest, "application/json")),
|
|
260
384
|
("filename[]", (filename, content, mime_type)),
|
|
261
385
|
],
|
|
262
386
|
timeout=60,
|
|
263
387
|
)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
388
|
+
ensure_response_status(
|
|
389
|
+
response,
|
|
390
|
+
success_statuses=(200, 201),
|
|
391
|
+
failure_message="GLPI v1 document upload failed",
|
|
392
|
+
)
|
|
269
393
|
payload = response.json()
|
|
270
394
|
if not isinstance(payload, dict):
|
|
271
395
|
raise ValueError(
|