glpi-python-client 0.3.4__tar.gz → 0.4.1__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.4 → glpi_python_client-0.4.1}/.gitignore +9 -1
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/.pre-commit-config.yaml +10 -0
- glpi_python_client-0.4.1/CHANGELOG.md +341 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/CONTRIBUTING.md +7 -4
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/PKG-INFO +23 -17
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/README.md +19 -13
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/api_reference.rst +132 -3
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/conf.py +0 -1
- glpi_python_client-0.4.1/docs/development.md +147 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/development_rtd.rst +6 -6
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/installation.rst +2 -2
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/user_guide.rst +268 -79
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/__init__.py +56 -6
- glpi_python_client-0.4.1/glpi_python_client/_async/__init__.py +15 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/_concurrency.py +59 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/_testing.py +146 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_async}/auth/__init__.py +3 -3
- glpi_python_client-0.4.1/glpi_python_client/_async/auth/_v1_session.py +466 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/auth/auth.py +417 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/__init__.py +17 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_async}/clients/_base_client.py +12 -13
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/__init__.py +54 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/administration/__init__.py +13 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/administration/_entity.py +217 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/administration/_user.py +224 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/assistance/__init__.py +8 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/assistance/_team.py +121 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/assistance/_ticket.py +238 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/assistance/timeline/__init__.py +23 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/assistance/timeline/_document.py +225 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/assistance/timeline/_followup.py +204 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/assistance/timeline/_solution.py +201 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/assistance/timeline/_task.py +194 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/dropdowns/__init__.py +7 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/dropdowns/_location.py +170 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/knowledgebase/__init__.py +21 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/knowledgebase/_article.py +214 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/knowledgebase/_category.py +120 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/knowledgebase/_comment.py +109 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/knowledgebase/_revision.py +70 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/management/__init__.py +7 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/management/_document.py +280 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/plugins/__init__.py +10 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/api/plugins/_fields.py +426 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/client.py +135 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/commons/__init__.py +9 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/commons/_config.py +317 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_async}/clients/commons/_constants.py +10 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_async}/clients/commons/_filters.py +12 -1
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/commons/_http.py +394 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/commons/_transport.py +573 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/custom/__init__.py +23 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/custom/_statistics.py +973 -0
- glpi_python_client-0.4.1/glpi_python_client/_async/clients/custom/_ticket_context.py +75 -0
- glpi_python_client-0.4.1/glpi_python_client/_errors.py +176 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/__init__.py +15 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/_concurrency.py +41 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/_testing.py +146 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/auth/__init__.py +13 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/auth/_v1_session.py +86 -35
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/auth/auth.py +162 -55
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/__init__.py +17 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/_base_client.py +177 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/__init__.py +54 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/administration/__init__.py +13 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/administration/_entity.py +14 -11
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/administration/_user.py +12 -11
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/assistance/__init__.py +8 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/assistance/_team.py +12 -10
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/assistance/_ticket.py +14 -11
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/assistance/timeline/__init__.py +23 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/assistance/timeline/_document.py +14 -11
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/assistance/timeline/_followup.py +15 -12
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/assistance/timeline/_solution.py +15 -12
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/assistance/timeline/_task.py +15 -12
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/dropdowns/__init__.py +7 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/dropdowns/_location.py +17 -11
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/knowledgebase/__init__.py +21 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/knowledgebase/_article.py +214 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/knowledgebase/_category.py +120 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/knowledgebase/_comment.py +109 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/knowledgebase/_revision.py +70 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/api/management/__init__.py +7 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/management/_document.py +22 -19
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/plugins/__init__.py +2 -2
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/api/plugins/_fields.py +35 -20
- glpi_python_client-0.3.4/glpi_python_client/clients/sync_client.py → glpi_python_client-0.4.1/glpi_python_client/_sync/clients/client.py +33 -27
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/commons/__init__.py +9 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/commons/_config.py +129 -46
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/commons/_constants.py +60 -0
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/commons/_filters.py +105 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/commons/_http.py +165 -25
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/commons/_payloads.py +42 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/commons/_transport.py +103 -81
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/custom/__init__.py +23 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_sync}/clients/custom/_statistics.py +340 -46
- glpi_python_client-0.4.1/glpi_python_client/_sync/clients/custom/_ticket_context.py +75 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/__init__.py +28 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/_content.py +1 -1
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/enums.py +18 -1
- glpi_python_client-0.4.1/glpi_python_client/models/api_schema/knowledgebase/__init__.py +45 -0
- glpi_python_client-0.4.1/glpi_python_client/models/api_schema/knowledgebase/_article.py +123 -0
- glpi_python_client-0.4.1/glpi_python_client/models/api_schema/knowledgebase/_category.py +78 -0
- glpi_python_client-0.4.1/glpi_python_client/models/api_schema/knowledgebase/_comment.py +77 -0
- glpi_python_client-0.4.1/glpi_python_client/models/api_schema/knowledgebase/_revision.py +35 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/plugins/_fields.py +2 -2
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/testing/__init__.py +2 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/testing/fixtures.py +9 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/testing/utils.py +17 -5
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/pyproject.toml +48 -5
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/skills/README.md +11 -2
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/skills/glpi-client-setup/SKILL.md +19 -10
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/skills/glpi-document-workflow/SKILL.md +4 -4
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/skills/glpi-reporting-and-context/SKILL.md +18 -5
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/skills/glpi-team-members/SKILL.md +3 -3
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/skills/glpi-ticket-timeline/SKILL.md +3 -3
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/skills/glpi-ticket-workflow/SKILL.md +4 -4
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/skills/glpi-user-location-provisioning/SKILL.md +4 -4
- glpi_python_client-0.3.4/docs/development.md +0 -109
- glpi_python_client-0.3.4/docs/glpi_api_contract.json +0 -188668
- glpi_python_client-0.3.4/glpi_python_client/auth/tests/test_auth.py +0 -189
- glpi_python_client-0.3.4/glpi_python_client/auth/tests/test_v1_session.py +0 -446
- glpi_python_client-0.3.4/glpi_python_client/clients/__init__.py +0 -20
- glpi_python_client-0.3.4/glpi_python_client/clients/api/__init__.py +0 -42
- glpi_python_client-0.3.4/glpi_python_client/clients/api/administration/__init__.py +0 -13
- glpi_python_client-0.3.4/glpi_python_client/clients/api/assistance/__init__.py +0 -8
- glpi_python_client-0.3.4/glpi_python_client/clients/api/assistance/timeline/__init__.py +0 -23
- glpi_python_client-0.3.4/glpi_python_client/clients/api/dropdowns/__init__.py +0 -7
- glpi_python_client-0.3.4/glpi_python_client/clients/api/management/__init__.py +0 -7
- glpi_python_client-0.3.4/glpi_python_client/clients/api/plugins/tests/__init__.py +0 -0
- glpi_python_client-0.3.4/glpi_python_client/clients/api/plugins/tests/test_fields_mixin.py +0 -377
- glpi_python_client-0.3.4/glpi_python_client/clients/async_client.py +0 -167
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/__init__.py +0 -9
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/_async_bridge.py +0 -191
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/_errors.py +0 -27
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/tests/__init__.py +0 -5
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/tests/test_errors.py +0 -41
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/tests/test_filters.py +0 -49
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/tests/test_http.py +0 -131
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/tests/test_payloads.py +0 -64
- glpi_python_client-0.3.4/glpi_python_client/clients/commons/tests/test_transport.py +0 -136
- glpi_python_client-0.3.4/glpi_python_client/clients/custom/__init__.py +0 -34
- glpi_python_client-0.3.4/glpi_python_client/clients/custom/_pagination_async.py +0 -164
- glpi_python_client-0.3.4/glpi_python_client/clients/custom/_statistics_async.py +0 -500
- glpi_python_client-0.3.4/glpi_python_client/clients/custom/_ticket_context.py +0 -67
- glpi_python_client-0.3.4/glpi_python_client/clients/custom/_ticket_context_async.py +0 -73
- glpi_python_client-0.3.4/glpi_python_client/clients/custom/tests/test_statistics.py +0 -562
- glpi_python_client-0.3.4/glpi_python_client/clients/custom/tests/test_statistics_async.py +0 -232
- glpi_python_client-0.3.4/glpi_python_client/clients/custom/tests/test_ticket_context.py +0 -57
- glpi_python_client-0.3.4/glpi_python_client/clients/tests/__init__.py +0 -1
- glpi_python_client-0.3.4/glpi_python_client/clients/tests/test_api_coverage.py +0 -882
- glpi_python_client-0.3.4/glpi_python_client/clients/tests/test_async_branches.py +0 -701
- glpi_python_client-0.3.4/glpi_python_client/clients/tests/test_async_smoke.py +0 -150
- glpi_python_client-0.3.4/glpi_python_client/clients/tests/test_glpi_client.py +0 -266
- glpi_python_client-0.3.4/glpi_python_client/clients/tests/test_parity.py +0 -70
- glpi_python_client-0.3.4/glpi_python_client/clients/tests/test_smoke.py +0 -259
- glpi_python_client-0.3.4/glpi_python_client/content/tests/__init__.py +0 -5
- glpi_python_client-0.3.4/glpi_python_client/content/tests/test_conversion.py +0 -13
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/administration/tests/__init__.py +0 -1
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/administration/tests/test_administration_schemas.py +0 -145
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/assistance/tests/__init__.py +0 -1
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/assistance/tests/test_assistance_schemas.py +0 -108
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/assistance/tests/test_content_roundtrip.py +0 -102
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/assistance/timeline/tests/__init__.py +0 -1
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/assistance/timeline/tests/test_timeline_schemas.py +0 -147
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/dropdowns/tests/__init__.py +0 -1
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/dropdowns/tests/test_dropdowns_schemas.py +0 -61
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/management/tests/__init__.py +0 -1
- glpi_python_client-0.3.4/glpi_python_client/models/api_schema/management/tests/test_management_schemas.py +0 -50
- 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 +0 -82
- glpi_python_client-0.3.4/glpi_python_client/models/custom_schema/tests/__init__.py +0 -1
- glpi_python_client-0.3.4/glpi_python_client/models/custom_schema/tests/test_ticket_context.py +0 -398
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/.readthedocs.yaml +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/LICENSE +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/_static/.gitkeep +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/index.rst +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/publishing.md +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/publishing_rtd.rst +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/docs/sponsoring.rst +0 -0
- {glpi_python_client-0.3.4/glpi_python_client → glpi_python_client-0.4.1/glpi_python_client/_async}/clients/commons/_payloads.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/content/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/content/conversion.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/_base.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/_common.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/administration/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/administration/_entity.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/administration/_user.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/assistance/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/assistance/_team.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/assistance/_ticket.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/dropdowns/_location.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/management/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/management/_document.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/api_schema/plugins/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/custom_schema/__init__.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/models/custom_schema/_ticket_context.py +0 -0
- {glpi_python_client-0.3.4 → glpi_python_client-0.4.1}/glpi_python_client/py.typed +0 -0
|
@@ -28,4 +28,12 @@ secrets/*
|
|
|
28
28
|
|
|
29
29
|
.claude/
|
|
30
30
|
|
|
31
|
-
CLAUDE.md
|
|
31
|
+
CLAUDE.md
|
|
32
|
+
|
|
33
|
+
docs/superpowers/*
|
|
34
|
+
|
|
35
|
+
# Large generated GLPI API contract (kept locally, not tracked)
|
|
36
|
+
docs/glpi_api_contract.json
|
|
37
|
+
# Coverage data: rewritten by every test run (and by the venv .pth hook).
|
|
38
|
+
.coverage
|
|
39
|
+
.coverage.*
|
|
@@ -17,6 +17,16 @@ repos:
|
|
|
17
17
|
types: [python]
|
|
18
18
|
pass_filenames: false
|
|
19
19
|
|
|
20
|
+
# Fails on a _sync/ tree left stale by an edit to _async/. Runs at
|
|
21
|
+
# commit time rather than pre-push so the generated tree is never
|
|
22
|
+
# committed out of step with its source.
|
|
23
|
+
- id: unasync-check
|
|
24
|
+
name: generated sync tree is up to date
|
|
25
|
+
entry: python unasync_build.py --check
|
|
26
|
+
language: system
|
|
27
|
+
types: [python]
|
|
28
|
+
pass_filenames: false
|
|
29
|
+
|
|
20
30
|
- id: pytest-coverage
|
|
21
31
|
name: pytest with coverage (>=95%)
|
|
22
32
|
entry: python -m pytest -m "not integration" --cov=glpi_python_client --cov-fail-under=95 -q
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## Unreleased
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **The unit test suite was published inside the wheel and the sdist.** Both
|
|
12
|
+
artefacts carried 56 test modules, so every install shipped the project's
|
|
13
|
+
own tests into the consuming environment. `[tool.hatch.build] exclude` now
|
|
14
|
+
drops `tests/` and `conftest.py`, taking the wheel from 180 entries to 126
|
|
15
|
+
and the sdist from 204 to 150. Nothing a downstream consumer imports was
|
|
16
|
+
removed: `glpi_python_client.testing` — the documented factories, fixtures
|
|
17
|
+
and fake responses — still ships in full, and the package now imports
|
|
18
|
+
cleanly in an environment with no `pytest` installed.
|
|
19
|
+
|
|
20
|
+
- **A `Major` priority ticket made the whole search fail.** GLPI's priority
|
|
21
|
+
scale has six levels; the published contract advertises five, and
|
|
22
|
+
`GlpiPriority` followed the contract. Since `GetTicket.priority` is typed
|
|
23
|
+
with that enum and validation runs per record, a single escalated ticket
|
|
24
|
+
anywhere in a result set raised `ValidationError` and took the entire
|
|
25
|
+
query down with it — most likely to bite exactly the reporting queries
|
|
26
|
+
that filter on high priority. `GlpiPriority.MAJOR = 6` is now defined.
|
|
27
|
+
The five existing members keep their identifiers, so stored filters are
|
|
28
|
+
unaffected. `urgency` and `impact` genuinely do stop at 5 and now accept a
|
|
29
|
+
value GLPI will never send, which is harmless in the direction that
|
|
30
|
+
matters.
|
|
31
|
+
|
|
32
|
+
- **The statistics layer sent GLPI v1 field names to the v2 API, which
|
|
33
|
+
silently ignored them and returned unfiltered results.** v2 drops a
|
|
34
|
+
`filter=` conjunct whose field it does not recognise, honours the rest,
|
|
35
|
+
and answers 200/206 with no error — so the aggregations narrowed by date
|
|
36
|
+
and looked plausible while ignoring the user and entity selection
|
|
37
|
+
entirely. Measured against a live GLPI 11 instance,
|
|
38
|
+
`get_user_activity` reported `tickets_as_technician == tickets_as_recipient
|
|
39
|
+
== 963` — the window's *total* ticket count — for every user regardless of
|
|
40
|
+
who they were. Corrected:
|
|
41
|
+
- `entities_id==N` → `entity.id==N` (v2 types `entity` as an object).
|
|
42
|
+
- `users_id_lastupdater==N` → `user_editor.id==N`.
|
|
43
|
+
- `users_id_requester==N` (as `user_recipient_id`) → `user_recipient.id==N`,
|
|
44
|
+
which is what that parameter's *name* has always meant. Note the v2
|
|
45
|
+
`user_recipient` field is `users_id_recipient` — who *recorded* the
|
|
46
|
+
ticket — not the requester link; the two are different people.
|
|
47
|
+
- `users_id_assign` / `users_id_requester` (as `user_id`) have **no v2
|
|
48
|
+
equivalent at all**: the v2 `team` array cannot be joined by the RSQL
|
|
49
|
+
engine (the four contract-declared subfields answer HTTP 500 and every
|
|
50
|
+
other spelling is silently ignored — 19 spellings were tested). These
|
|
51
|
+
now resolve through the legacy v1 search engine, whose searchOption 5
|
|
52
|
+
(`Technicien`) and 4 (`Demandeur`) map exactly onto the
|
|
53
|
+
`glpi_tickets_users` link types, and which fails **loudly** (HTTP 400)
|
|
54
|
+
on an unknown field instead of silently returning everything.
|
|
55
|
+
- **`rsql_any_filter` produced an unparenthesised OR group**, and RSQL binds
|
|
56
|
+
`;` (AND) tighter than `,` (OR). `get_ticket_statistics(entity_name=...)`
|
|
57
|
+
matching several entities emitted `date;e==1,e==2`, which the server reads
|
|
58
|
+
as `(date AND e==1) OR e==2` — the date window stopped applying to every
|
|
59
|
+
entity after the first. Measured live: 16,245 tickets returned where the
|
|
60
|
+
correct answer was 1,552. OR groups are now wrapped in parentheses.
|
|
61
|
+
- **v2 ticket searches counted soft-deleted tickets.** The v2 search includes
|
|
62
|
+
trashed tickets by default while v1 excludes them (59,690 live + 258
|
|
63
|
+
trashed = 59,948), so every aggregation was inflated by the trash bin — for
|
|
64
|
+
one user 92% of matches were deleted tickets. All v2 ticket queries in the
|
|
65
|
+
statistics layer now pin `is_deleted==false`.
|
|
66
|
+
- Actor identifiers are validated before reaching the v1 search, which fails
|
|
67
|
+
*open* rather than rejecting bad input: `equals 0` matched 20,905 tickets
|
|
68
|
+
(a LEFT-JOIN-NULL "has no actor" match), an empty value matched the entire
|
|
69
|
+
baseline, and a non-numeric value returned the same arbitrary 3 rows
|
|
70
|
+
whatever the string. A non-positive or non-`int` id now raises
|
|
71
|
+
`GlpiValidationError`.
|
|
72
|
+
- **The per-ticket task fan-out is replaced by one bulk sweep.** The v2 API
|
|
73
|
+
publishes tasks only under `/Assistance/Ticket/{id}/Timeline/Task`, so
|
|
74
|
+
aggregating N tickets cost N requests. The v1 `TicketTask` *collection*
|
|
75
|
+
returns whole rows including `tickets_id`, paged 1000 at a time, so the
|
|
76
|
+
same aggregate now costs one page per 1000 tasks created since the window
|
|
77
|
+
opened. Measured live on a 120-ticket set: **120 requests / 11.7 s -> 2
|
|
78
|
+
requests / 0.4 s**, with `ticket_count`, `task_count`, `total_duration`,
|
|
79
|
+
`duration_by_ticket` and `duration_by_user` all byte-identical between the
|
|
80
|
+
two paths. Below 25 tickets the per-ticket path is cheaper and is kept, so
|
|
81
|
+
clients without a v1 session are unaffected.
|
|
82
|
+
|
|
83
|
+
Note v1 `search/TicketTask` is *not* usable for this: its searchOptions
|
|
84
|
+
expose the task id, content, category, date, privacy, technician, duration
|
|
85
|
+
and state, but no parent ticket id, so results cannot be attributed back
|
|
86
|
+
to a ticket. The plain collection endpoint is what carries `tickets_id`.
|
|
87
|
+
|
|
88
|
+
- `get_user_activity` walks the date window **once** for all users instead of
|
|
89
|
+
twice per user. Combined with the corrected filters this took one user over
|
|
90
|
+
90 days from **979 requests / 120 s to 9 requests / 5.1 s**, verified live.
|
|
91
|
+
|
|
92
|
+
Actor-based statistics now require the legacy v1 session (`v1_base_url` +
|
|
93
|
+
`v1_user_token`) and raise `RuntimeError` naming the missing options when
|
|
94
|
+
it is absent, rather than returning a wrong number.
|
|
95
|
+
|
|
96
|
+
- `GLPITokenManager._refresh_access_token`'s retry decorator no longer
|
|
97
|
+
retries a `GlpiServerError` from its fall-through to the nested
|
|
98
|
+
`_acquire_token()` call. That nested call already carries its own
|
|
99
|
+
independent 3-attempt retry decorator for `GlpiServerError`, so the
|
|
100
|
+
outer decorator retrying it too meant a persistent 5xx during token
|
|
101
|
+
refresh cost 3 (outer attempts) × (1 refresh POST + 3 nested acquire
|
|
102
|
+
POSTs) = 12 POST requests and ~33s of `wait_fixed(3)` sleep, instead of
|
|
103
|
+
the 3 attempts the retry configuration alone would suggest. The outer
|
|
104
|
+
decorator now only retries `requests.RequestException` (a genuine
|
|
105
|
+
network fault on the refresh POST itself), which is not covered by the
|
|
106
|
+
nested call at all. A persistent 5xx now costs exactly 1 refresh POST +
|
|
107
|
+
3 nested acquire POSTs = 4 POST requests. A persistent 401 (2 POSTs) and
|
|
108
|
+
a network error on the refresh POST (3 POSTs) are unaffected.
|
|
109
|
+
- `AsyncGlpiClient.create_kb_article` / `update_kb_article` no longer
|
|
110
|
+
silently drop `categories`. Both methods called the public
|
|
111
|
+
`set_kb_article_categories` through `self` from inside a synchronous
|
|
112
|
+
method body; `AsyncBridge.__init_subclass__` wraps every public sync
|
|
113
|
+
method into a coroutine, so that call returned an un-awaited coroutine
|
|
114
|
+
instead of performing the write. The article was created (or updated)
|
|
115
|
+
successfully, a valid id was returned, and no exception was raised —
|
|
116
|
+
the category assignment simply never happened. Fixed with hand-written
|
|
117
|
+
async overrides in `_article_async.py` that strip `categories` from the
|
|
118
|
+
v2 body, run the v2 write in a worker thread, and apply the category
|
|
119
|
+
fallback through an awaited call.
|
|
120
|
+
- `AsyncGlpiClient.get_ticket_custom_fields` / `set_ticket_custom_fields`
|
|
121
|
+
raised `TypeError: 'coroutine' object is not iterable` and were
|
|
122
|
+
unusable. Same root cause as above: a sync method reaching a sibling
|
|
123
|
+
public method through `self` received a coroutine instead of a result.
|
|
124
|
+
Fixed with hand-written async overrides in `_fields_async.py`.
|
|
125
|
+
- The integration suite is runnable end-to-end again. Two defects, both in
|
|
126
|
+
`integration_tests/` only (no library code involved):
|
|
127
|
+
- `test_iter_search_tickets_multi_page` walked *every* matching ticket in
|
|
128
|
+
batches of 3 with no upper bound — it was the only one of the suite's
|
|
129
|
+
seven `iter_search` loops missing a `break`. Against a real instance
|
|
130
|
+
(59,879 matching tickets) that is ~19,960 requests and several hours,
|
|
131
|
+
which stalled the whole suite. It now stops after 3 pages and asserts
|
|
132
|
+
that ids do not repeat across pages, which actually verifies that the
|
|
133
|
+
`start` offset advances — the old unbounded loop asserted only
|
|
134
|
+
`isinstance(collected, list)` and so could not have detected a stuck
|
|
135
|
+
offset.
|
|
136
|
+
- The three GLPI Fields plugin tests failed rather than skipped when the
|
|
137
|
+
plugin is not installed. `_skip_when_no_v1` only checked that v1
|
|
138
|
+
*credentials were configured*, never that the *plugin existed*; an
|
|
139
|
+
absent plugin makes GLPI reject the `PluginFieldsContainer` itemtype
|
|
140
|
+
with a 400 rather than return an empty list. A new `fields_containers`
|
|
141
|
+
fixture skips on exactly that signature (400 +
|
|
142
|
+
`ERROR_RESOURCE_NOT_FOUND_NOR_COMMONDBTM`) and re-raises anything else.
|
|
143
|
+
- `parse_optional_env_int` (environment/config parsing) and
|
|
144
|
+
`StatisticsMixin._resolve_window` (the date-window helper behind
|
|
145
|
+
`get_ticket_statistics` / `get_task_durations` / `get_user_activity`)
|
|
146
|
+
no longer let a malformed value escape as a bare stdlib `ValueError`
|
|
147
|
+
from `int()` / `date.fromisoformat()` (e.g. `GLPI_TIMEOUT=abc` or
|
|
148
|
+
`get_ticket_statistics(start_date="2026-13-45")`). Both now raise
|
|
149
|
+
`GlpiValidationError`, chaining the original error via `from` rather
|
|
150
|
+
than swallowing it. Non-breaking: `GlpiValidationError` inherits
|
|
151
|
+
`ValueError`, so `except ValueError` still catches it.
|
|
152
|
+
|
|
153
|
+
### Added
|
|
154
|
+
|
|
155
|
+
- `glpi_python_client/clients/tests/test_async_selfcall_guard.py`: a
|
|
156
|
+
structural AST guard that fails the suite if any public method on
|
|
157
|
+
`GlpiClient` transitively reaches another public method through a
|
|
158
|
+
literal `self.name(...)` call (directly, or via a private helper)
|
|
159
|
+
without a corresponding hand-written async override on
|
|
160
|
+
`AsyncGlpiClient`. This prevents the same bug class — silent data loss
|
|
161
|
+
or a `TypeError` at call time, depending on how the dropped coroutine is
|
|
162
|
+
used — from being reintroduced by a future endpoint.
|
|
163
|
+
- A public exception hierarchy, exported from the package root:
|
|
164
|
+
`GlpiError`, `GlpiTransportError`, `GlpiTimeoutError`, `GlpiStatusError`,
|
|
165
|
+
`GlpiAuthError`, `GlpiNotFoundError`, `GlpiServerError`,
|
|
166
|
+
`GlpiValidationError` and `GlpiProtocolError`. `GlpiStatusError` and its
|
|
167
|
+
subclasses carry `.status_code`, `.url` and `.response_text`. A GLPI 404
|
|
168
|
+
and a bad argument were previously both a bare `ValueError` and could not
|
|
169
|
+
be told apart.
|
|
170
|
+
- `FakeResponse` (in the public `glpi_python_client.testing` module) gained
|
|
171
|
+
a `url` attribute.
|
|
172
|
+
- A user-guide "Error handling" section documenting the exception
|
|
173
|
+
hierarchy and the retry behaviour for both the transport layer and OAuth
|
|
174
|
+
token acquisition/refresh.
|
|
175
|
+
|
|
176
|
+
### Changed
|
|
177
|
+
|
|
178
|
+
- **`AsyncGlpiClient` now performs real non-blocking I/O.** It was a facade
|
|
179
|
+
that wrapped each synchronous method in `asyncio.to_thread`, so "async"
|
|
180
|
+
meant "blocking call on a worker thread". It is now genuinely
|
|
181
|
+
asynchronous, built on `httpx.AsyncClient`, with no thread pool and no
|
|
182
|
+
executor. The `executor` constructor keyword is gone, as is
|
|
183
|
+
`AsyncBridge`.
|
|
184
|
+
- The two clients are now one codebase. `glpi_python_client/_async/` is
|
|
185
|
+
hand-written and `glpi_python_client/_sync/` is generated from it by
|
|
186
|
+
`unasync_build.py`, committed, and diffed in CI. Endpoint logic exists
|
|
187
|
+
exactly once, so the two surfaces cannot drift.
|
|
188
|
+
- This deletes the six hand-written async override modules the bridge
|
|
189
|
+
forced into existence — including the 500-line `_statistics_async.py`,
|
|
190
|
+
which duplicated the most intricate logic in the package with no test
|
|
191
|
+
asserting the two copies agreed.
|
|
192
|
+
- Aggregating helpers keep their concurrency through a shared `gather`
|
|
193
|
+
helper that is `asyncio.gather` on the async surface and sequential
|
|
194
|
+
evaluation on the generated one, written once at the call site.
|
|
195
|
+
- **Public imports are unchanged**: `from glpi_python_client import
|
|
196
|
+
GlpiClient, AsyncGlpiClient` still works. Code importing private
|
|
197
|
+
module paths (`glpi_python_client.clients.*`, `glpi_python_client.auth.*`)
|
|
198
|
+
must add the tree segment, e.g.
|
|
199
|
+
`glpi_python_client._sync.clients.commons._transport`.
|
|
200
|
+
- **Breaking: the HTTP transport moved from `requests` to `httpx`.**
|
|
201
|
+
`requests` and `urllib3` are no longer dependencies. The v2 transport, the
|
|
202
|
+
legacy v1 session, and the OAuth token manager were swapped together in a
|
|
203
|
+
single change because they share `_http.py`; splitting them would have left
|
|
204
|
+
the shared code validated only by tests exercising the old transport.
|
|
205
|
+
Behaviour is preserved, which took three deliberate corrections where the
|
|
206
|
+
two libraries disagree and the difference is silent:
|
|
207
|
+
- **Query parameters with a `None` value are dropped**, as `requests` did.
|
|
208
|
+
`httpx` encodes them as a valueless `key=`, and GLPI treats an empty
|
|
209
|
+
filter or search value as *match everything* — so the swap would have
|
|
210
|
+
silently widened queries rather than leaving them unconstrained.
|
|
211
|
+
- **`bytes` and `bool` parameter values keep their previous rendering**
|
|
212
|
+
(`b"x"` → `x`, `True` → `True`). `httpx` would emit the Python repr
|
|
213
|
+
`b'x'` and a lowercase `true`.
|
|
214
|
+
- **Redirects are still followed.** `requests` follows them by default and
|
|
215
|
+
`httpx` does not, so a followed redirect would have started surfacing as
|
|
216
|
+
a bare 3xx response.
|
|
217
|
+
- **Breaking: network-level faults now raise `GlpiTransportError`** (or its
|
|
218
|
+
`GlpiTimeoutError` subclass) instead of propagating the HTTP library's own
|
|
219
|
+
exception. This completes the promise the previous release documented:
|
|
220
|
+
catching `GlpiError` is now sufficient for the library's whole failure
|
|
221
|
+
surface, and you never need to import the HTTP library. The originating
|
|
222
|
+
exception is attached as `__cause__`. Code doing
|
|
223
|
+
`except requests.RequestException` should now catch `GlpiTransportError`.
|
|
224
|
+
Note it does *not* inherit `ValueError` — nothing was passed in wrongly and
|
|
225
|
+
no value came back.
|
|
226
|
+
- The retry predicates were retargeted onto this library-owned type in the
|
|
227
|
+
same change. This is the failure mode that made the swap risky: the
|
|
228
|
+
exception trees of the two libraries are completely disjoint, so a
|
|
229
|
+
predicate left naming the old one stops matching and **every retry
|
|
230
|
+
silently disappears** — no error, no warning, and a green test suite.
|
|
231
|
+
Naming a type the library itself raises makes that impossible to
|
|
232
|
+
reintroduce. A mutation test confirms the suite catches it: reverting the
|
|
233
|
+
predicate fails 7 tests across all three transports.
|
|
234
|
+
- **Breaking:** a persistent 5xx now raises `GlpiServerError` instead of
|
|
235
|
+
`tenacity.RetryError`. The retry decorators gained `reraise=True`. Code
|
|
236
|
+
doing `except tenacity.RetryError` and digging out
|
|
237
|
+
`.last_attempt.exception()` should now catch `GlpiServerError` directly.
|
|
238
|
+
- **Breaking:** unexpected HTTP statuses raise a `GlpiStatusError` subclass;
|
|
239
|
+
rejected arguments and configuration raise `GlpiValidationError`; 2xx
|
|
240
|
+
responses with an unusable body raise `GlpiProtocolError`. All three
|
|
241
|
+
inherit `ValueError`, so existing `except ValueError` handlers keep
|
|
242
|
+
working.
|
|
243
|
+
- **Breaking:** a non-2xx OAuth token response raises `GlpiAuthError` (401/403)
|
|
244
|
+
or `GlpiServerError` (5xx). The token retry decorators had no `retry=`
|
|
245
|
+
predicate and therefore retried every failure, including a rejected
|
|
246
|
+
credential; a wrong `client_secret` cost 3 attempts and 6 seconds. OAuth
|
|
247
|
+
4xx is now final, matching the rest of the library. OAuth 5xx is still
|
|
248
|
+
retried.
|
|
249
|
+
- **Breaking:** the private `glpi_python_client.clients.commons._errors`
|
|
250
|
+
module and its `remote_error_message` helper are removed. It had no
|
|
251
|
+
library call sites, and `reraise=True` leaves it nothing to unwrap.
|
|
252
|
+
|
|
253
|
+
### Performance
|
|
254
|
+
|
|
255
|
+
- **`sniffio` is now a dependency, and the async client is ~2.6x faster at
|
|
256
|
+
wide fan-out because of it.** `httpcore` decides whether it is running
|
|
257
|
+
under asyncio or trio by probing for `sniffio` on every async request,
|
|
258
|
+
falling back to `"asyncio"` when the import fails. Nothing in the
|
|
259
|
+
dependency chain required it — `httpx` pulls in `anyio`, and `anyio` 4.14
|
|
260
|
+
dropped `sniffio` — so a fresh install had no `sniffio`, and because
|
|
261
|
+
Python never caches a failed import, every single request re-walked
|
|
262
|
+
`sys.path` doing filesystem stats. Measured against a local server with
|
|
263
|
+
50 ms latency, a fan-out of 128 took **3354 ms without `sniffio` and
|
|
264
|
+
1304 ms with it**. `pip check` reports no broken requirements either way,
|
|
265
|
+
which is why this went unnoticed: nothing declares the package, nothing
|
|
266
|
+
imports it, and the only symptom is that every request is slower.
|
|
267
|
+
|
|
268
|
+
- **Bounding a wide fan-out is now a documented requirement, not a
|
|
269
|
+
suggestion.** `httpcore` rescans its entire connection pool on every
|
|
270
|
+
request assignment, calling `has_expired()` per connection — profiled at
|
|
271
|
+
9040 such calls for a 64-request fan-out. The cost is quadratic in the
|
|
272
|
+
width of the fan-out and it saturates the event loop, so server-observed
|
|
273
|
+
concurrency *falls* as the fan-out widens. Raising `httpx.Limits` does not
|
|
274
|
+
help. At a fan-out of 16 against a 50 ms server, an unbounded
|
|
275
|
+
`gather` took 350 ms while the same work capped at 16 with an
|
|
276
|
+
`asyncio.Semaphore` took 108 ms. See "Bounding concurrency" in the user
|
|
277
|
+
guide. This is a property of `httpx` 0.28 / `httpcore` 1.0.9, which are
|
|
278
|
+
the current releases; there is no version to upgrade to.
|
|
279
|
+
|
|
280
|
+
### Documentation
|
|
281
|
+
|
|
282
|
+
- **The documentation still described the deleted bridge**, in the places
|
|
283
|
+
users actually read: the README, the user guide, the API reference, the
|
|
284
|
+
package docstring, and two skills all said `AsyncGlpiClient` wraps each
|
|
285
|
+
synchronous method into a coroutine dispatched to a worker thread via
|
|
286
|
+
`asyncio.to_thread`. None of that has been true since the codegen
|
|
287
|
+
rewrite. The worst of it was live API: the user guide's *Custom thread
|
|
288
|
+
pools* section and step 8 of `glpi-client-setup` documented an
|
|
289
|
+
`executor=` constructor argument that no longer exists, so anyone
|
|
290
|
+
copying either example got a `TypeError`. That section is now
|
|
291
|
+
*Bounding concurrency* and shows an `asyncio.Semaphore`, which is what
|
|
292
|
+
actually bounds a fan-out now.
|
|
293
|
+
- **Fourteen docstring cross-references pointed at modules the rewrite had
|
|
294
|
+
renamed or deleted** (`clients.async_client`, `clients.sync_client`,
|
|
295
|
+
`custom._ticket_context_async`). Nothing caught them: Sphinx runs with
|
|
296
|
+
`nitpicky` off, so an unresolvable target renders as plain text rather
|
|
297
|
+
than failing the build, and these private modules are not autodoc'd in
|
|
298
|
+
the first place. `tests/test_docstring_references.py` now resolves every
|
|
299
|
+
qualified reference in the package against the live modules.
|
|
300
|
+
- **The generated sync tree documented itself in terms of the async one.**
|
|
301
|
+
unasync repoints imports, where `_async` is its own NAME token, but a
|
|
302
|
+
dotted path inside a docstring is a single string token and passes
|
|
303
|
+
through untouched — so 47 cross-references in the shipped sync client
|
|
304
|
+
pointed into `_async/`. The diff gate is blind to this for the same
|
|
305
|
+
reason it is blind to a token collision: the omission is deterministic,
|
|
306
|
+
so regeneration reproduces it and the diff stays clean. `unasync_build`
|
|
307
|
+
now repoints the qualified prefix, and a test asserts the generated tree
|
|
308
|
+
never names `_async` at all.
|
|
309
|
+
- `TicketContextMixin` claimed its five calls ran sequentially and that an
|
|
310
|
+
async override fanned them out, contradicting both the code and its own
|
|
311
|
+
method docstring. The development guide still described the deleted
|
|
312
|
+
`_ticket_context_async.py` / `_statistics_async.py` and the retired
|
|
313
|
+
`test_parity.py` / `test_async_selfcall_guard.py` suites.
|
|
314
|
+
- The `requests` intersphinx mapping is removed; it survived the transport
|
|
315
|
+
swap and made every docs build fetch an inventory nothing referenced.
|
|
316
|
+
|
|
317
|
+
### Unchanged (deliberately)
|
|
318
|
+
|
|
319
|
+
- Retry semantics: 5xx retried 3 times with a 3-second fixed wait, 4xx never
|
|
320
|
+
retried.
|
|
321
|
+
- Tolerant search endpoints still return `[]` rather than raising on a 4xx.
|
|
322
|
+
- The `TypeError` sites in environment parsing and the `RuntimeError` sites
|
|
323
|
+
for closed clients, missing v1 sessions and partial KB failures still
|
|
324
|
+
raise those types. `GlpiValidationError` inherits `ValueError`, not
|
|
325
|
+
`TypeError`, so converting them would break `except TypeError` callers.
|
|
326
|
+
- The transport is still `requests`. Network faults (connection reset, DNS,
|
|
327
|
+
timeout) still surface as `requests` exceptions; they become
|
|
328
|
+
`GlpiTransportError` / `GlpiTimeoutError` when the transport moves to
|
|
329
|
+
httpx, with no change to the class names above.
|
|
330
|
+
|
|
331
|
+
### Notes
|
|
332
|
+
|
|
333
|
+
- Both fixed bugs shared one root cause: `AsyncBridge` wraps every public
|
|
334
|
+
sync method into a coroutine, so a sync method body calling a sibling
|
|
335
|
+
public method through `self` (rather than through a hand-written async
|
|
336
|
+
override) silently receives a coroutine instead of the real return
|
|
337
|
+
value.
|
|
338
|
+
- This is a documentation-only release note; **no version was released**
|
|
339
|
+
from this branch. The next release is planned as 0.4.0, an httpx +
|
|
340
|
+
unasync rewrite that removes `AsyncBridge` entirely, making this class
|
|
341
|
+
of bug structurally impossible rather than merely guarded against.
|
|
@@ -51,10 +51,13 @@ python -m sphinx -W --keep-going -b html docs docs/_build/html
|
|
|
51
51
|
## Design Guidelines
|
|
52
52
|
|
|
53
53
|
- Keep API calls behind `GlpiClient` / `AsyncGlpiClient` methods. Add
|
|
54
|
-
new endpoints to
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
new endpoints to the async endpoint mixin under
|
|
55
|
+
`glpi_python_client/_async/`, then run `python unasync_build.py` to
|
|
56
|
+
regenerate `glpi_python_client/_sync/` and commit both. Never edit
|
|
57
|
+
`_sync/` by hand; CI regenerates it and fails on any difference. For
|
|
58
|
+
concurrent fan-out use `gather` from `_async/_concurrency.py` rather
|
|
59
|
+
than `asyncio.gather` directly, so the generated sync code stays
|
|
60
|
+
correct.
|
|
58
61
|
- Prefer field-validated Pydantic models for request and response payloads.
|
|
59
62
|
- Avoid organization-specific category, entity, or profile defaults in the
|
|
60
63
|
library core.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glpi-python-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A typed Python client for GLPI ITSM APIs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/baraline/glpi_python_client
|
|
6
6
|
Project-URL: Documentation, https://glpi-python-client.readthedocs.io/en/latest/
|
|
@@ -24,14 +24,14 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
24
24
|
Classifier: Typing :: Typed
|
|
25
25
|
Requires-Python: >=3.10
|
|
26
26
|
Requires-Dist: beautifulsoup4>=4.12
|
|
27
|
+
Requires-Dist: httpx>=0.28
|
|
27
28
|
Requires-Dist: lxml>=4.9
|
|
28
29
|
Requires-Dist: markdown>=3.6
|
|
29
30
|
Requires-Dist: markdownify>=0.13
|
|
30
31
|
Requires-Dist: pydantic>=2.8
|
|
31
|
-
Requires-Dist:
|
|
32
|
+
Requires-Dist: sniffio>=1.3
|
|
32
33
|
Requires-Dist: tenacity>=8.2
|
|
33
34
|
Requires-Dist: typing-extensions>=4.7; python_version < '3.11'
|
|
34
|
-
Requires-Dist: urllib3>=2.0
|
|
35
35
|
Provides-Extra: dev
|
|
36
36
|
Requires-Dist: build>=1.2; extra == 'dev'
|
|
37
37
|
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
@@ -45,7 +45,7 @@ Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'dev'
|
|
|
45
45
|
Requires-Dist: sphinx<8.2,>=7.2; extra == 'dev'
|
|
46
46
|
Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'dev'
|
|
47
47
|
Requires-Dist: twine>=5.1; extra == 'dev'
|
|
48
|
-
Requires-Dist:
|
|
48
|
+
Requires-Dist: unasync>=0.6; extra == 'dev'
|
|
49
49
|
Requires-Dist: vulture>=2.11; extra == 'dev'
|
|
50
50
|
Provides-Extra: docs
|
|
51
51
|
Requires-Dist: numpydoc>=1.8; extra == 'docs'
|
|
@@ -73,10 +73,12 @@ back to HTML for outgoing payloads.
|
|
|
73
73
|
It currently focuses on ticket-centric workflows and exposes two high-level
|
|
74
74
|
clients built on top of the GLPI v2 REST API:
|
|
75
75
|
|
|
76
|
-
- `GlpiClient` — synchronous, blocking client
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
- `GlpiClient` — synchronous, blocking client.
|
|
77
|
+
- `AsyncGlpiClient` — asynchronous client doing real non-blocking I/O on the
|
|
78
|
+
event loop.
|
|
79
|
+
|
|
80
|
+
Neither wraps the other. The async tree is hand-written and the sync one is
|
|
81
|
+
generated from it, so the two surfaces cannot drift apart.
|
|
80
82
|
|
|
81
83
|
Note that all integration tests using this package are made on GLPI 11.
|
|
82
84
|
I cannot make any guarantee of the behaviour on previous versions.
|
|
@@ -162,15 +164,19 @@ variables.
|
|
|
162
164
|
### Sync or async?
|
|
163
165
|
|
|
164
166
|
Both clients expose the exact same endpoint surface and accept the same
|
|
165
|
-
constructor arguments
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
`
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
`
|
|
167
|
+
constructor arguments, because both are generated from one source: the
|
|
168
|
+
async tree is hand-written and the sync one is produced from it by a build
|
|
169
|
+
step that strips `async`/`await`. `AsyncGlpiClient` performs real
|
|
170
|
+
non-blocking I/O on the event loop — there is no worker thread and no
|
|
171
|
+
executor — and `GlpiClient` performs real blocking I/O with no coroutine
|
|
172
|
+
scheduling. An auth lock serialises OAuth token acquisition on both
|
|
173
|
+
surfaces, so concurrent fan-outs cannot race.
|
|
174
|
+
|
|
175
|
+
Pick `GlpiClient` for plain scripts, CLI tools, and synchronous services;
|
|
176
|
+
pick `AsyncGlpiClient` when your application already runs an event loop or
|
|
177
|
+
when you need concurrent fan-out (the aggregated `get_ticket_context` and
|
|
178
|
+
per-ticket `get_task_statistics` helpers overlap their calls there, and run
|
|
179
|
+
them one after another on the sync client).
|
|
174
180
|
|
|
175
181
|
## Documentation
|
|
176
182
|
|
|
@@ -17,10 +17,12 @@ back to HTML for outgoing payloads.
|
|
|
17
17
|
It currently focuses on ticket-centric workflows and exposes two high-level
|
|
18
18
|
clients built on top of the GLPI v2 REST API:
|
|
19
19
|
|
|
20
|
-
- `GlpiClient` — synchronous, blocking client
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
- `GlpiClient` — synchronous, blocking client.
|
|
21
|
+
- `AsyncGlpiClient` — asynchronous client doing real non-blocking I/O on the
|
|
22
|
+
event loop.
|
|
23
|
+
|
|
24
|
+
Neither wraps the other. The async tree is hand-written and the sync one is
|
|
25
|
+
generated from it, so the two surfaces cannot drift apart.
|
|
24
26
|
|
|
25
27
|
Note that all integration tests using this package are made on GLPI 11.
|
|
26
28
|
I cannot make any guarantee of the behaviour on previous versions.
|
|
@@ -106,15 +108,19 @@ variables.
|
|
|
106
108
|
### Sync or async?
|
|
107
109
|
|
|
108
110
|
Both clients expose the exact same endpoint surface and accept the same
|
|
109
|
-
constructor arguments
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
`
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
`
|
|
111
|
+
constructor arguments, because both are generated from one source: the
|
|
112
|
+
async tree is hand-written and the sync one is produced from it by a build
|
|
113
|
+
step that strips `async`/`await`. `AsyncGlpiClient` performs real
|
|
114
|
+
non-blocking I/O on the event loop — there is no worker thread and no
|
|
115
|
+
executor — and `GlpiClient` performs real blocking I/O with no coroutine
|
|
116
|
+
scheduling. An auth lock serialises OAuth token acquisition on both
|
|
117
|
+
surfaces, so concurrent fan-outs cannot race.
|
|
118
|
+
|
|
119
|
+
Pick `GlpiClient` for plain scripts, CLI tools, and synchronous services;
|
|
120
|
+
pick `AsyncGlpiClient` when your application already runs an event loop or
|
|
121
|
+
when you need concurrent fan-out (the aggregated `get_ticket_context` and
|
|
122
|
+
per-ticket `get_task_statistics` helpers overlap their calls there, and run
|
|
123
|
+
them one after another on the sync client).
|
|
118
124
|
|
|
119
125
|
## Documentation
|
|
120
126
|
|