glpi-python-client 0.4.3__tar.gz → 0.5.0__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.4.3 → glpi_python_client-0.5.0}/.gitignore +5 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/CHANGELOG.md +380 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/PKG-INFO +6 -2
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/README.md +5 -1
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/api_reference.rst +56 -5
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/development.md +12 -1
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/user_guide.rst +81 -8
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/__init__.py +3 -1
- {glpi_python_client-0.4.3/glpi_python_client/_sync → glpi_python_client-0.5.0/glpi_python_client/_async}/clients/commons/_payloads.py +8 -6
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_errors.py +36 -4
- {glpi_python_client-0.4.3/glpi_python_client/_async → glpi_python_client-0.5.0/glpi_python_client/_sync}/clients/commons/_payloads.py +8 -6
- glpi_python_client-0.5.0/glpi_python_client/content/conversion.py +729 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/__init__.py +4 -3
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/_base.py +135 -6
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/__init__.py +7 -0
- glpi_python_client-0.5.0/glpi_python_client/models/api_schema/_content.py +292 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/assistance/_ticket.py +30 -9
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/assistance/timeline/_followup.py +29 -8
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/assistance/timeline/_solution.py +29 -8
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/assistance/timeline/_task.py +29 -8
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/knowledgebase/_article.py +49 -7
- glpi_python_client-0.5.0/glpi_python_client/models/api_schema/knowledgebase/_revision.py +58 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/custom_schema/_ticket_context.py +11 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/rsql.py +74 -14
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/pyproject.toml +1 -1
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-client-setup/SKILL.md +13 -4
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-document-workflow/SKILL.md +1 -1
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-knowledge-base/SKILL.md +3 -1
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-plugin-fields/SKILL.md +2 -2
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-reporting-and-context/SKILL.md +2 -2
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-team-members/SKILL.md +1 -1
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-ticket-timeline/SKILL.md +3 -2
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-ticket-workflow/SKILL.md +4 -2
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/glpi-user-location-provisioning/SKILL.md +1 -1
- glpi_python_client-0.4.3/glpi_python_client/content/conversion.py +0 -130
- glpi_python_client-0.4.3/glpi_python_client/models/api_schema/_content.py +0 -83
- glpi_python_client-0.4.3/glpi_python_client/models/api_schema/knowledgebase/_revision.py +0 -35
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/.pre-commit-config.yaml +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/.readthedocs.yaml +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/CONTRIBUTING.md +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/LICENSE +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/_static/.gitkeep +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/conf.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/development_rtd.rst +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/index.rst +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/installation.rst +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/publishing.md +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/publishing_rtd.rst +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/docs/sponsoring.rst +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/_concurrency.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/_testing.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/auth/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/auth/_v1_session.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/auth/auth.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/_base_client.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/administration/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/administration/_entity.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/administration/_user.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/assistance/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/assistance/_team.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/assistance/_ticket.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/dropdowns/_location.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/knowledgebase/_article.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/knowledgebase/_revision.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/management/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/management/_document.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/plugins/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/api/plugins/_fields.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/client.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/commons/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/commons/_config.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/commons/_constants.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/commons/_filters.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/commons/_http.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/commons/_transport.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/custom/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/custom/_statistics.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_async/clients/custom/_ticket_context.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/_concurrency.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/_testing.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/auth/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/auth/_v1_session.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/auth/auth.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/_base_client.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/administration/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/administration/_entity.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/administration/_user.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/assistance/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/assistance/_team.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/assistance/_ticket.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/dropdowns/_location.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/knowledgebase/_article.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/knowledgebase/_revision.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/management/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/management/_document.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/plugins/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/api/plugins/_fields.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/client.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/commons/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/commons/_config.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/commons/_constants.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/commons/_filters.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/commons/_http.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/commons/_transport.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/custom/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/custom/_statistics.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/_sync/clients/custom/_ticket_context.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/content/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/_common.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/administration/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/administration/_entity.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/administration/_user.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/assistance/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/assistance/_team.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/dropdowns/_location.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/enums.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/management/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/management/_document.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/plugins/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/api_schema/plugins/_fields.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/models/custom_schema/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/py.typed +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/testing/__init__.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/testing/fixtures.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/glpi_python_client/testing/utils.py +0 -0
- {glpi_python_client-0.4.3 → glpi_python_client-0.5.0}/skills/README.md +0 -0
|
@@ -37,3 +37,8 @@ docs/glpi_api_contract.json
|
|
|
37
37
|
# Coverage data: rewritten by every test run (and by the venv .pth hook).
|
|
38
38
|
.coverage
|
|
39
39
|
.coverage.*
|
|
40
|
+
|
|
41
|
+
# One-shot live-instance probe scripts. They are standalone investigations
|
|
42
|
+
# (a main() run by hand against preprod), not collected tests -- the findings
|
|
43
|
+
# get written up in CHANGELOG.md, the scripts stay local.
|
|
44
|
+
integration_tests/probe_*.py
|
|
@@ -4,6 +4,361 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## 0.5.0 — 2026-09-08
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Deeply nested HTML raised `RecursionError` while a model was being
|
|
12
|
+
validated.** `markdownify` walks the parsed document recursively and
|
|
13
|
+
spends about two CPython frames per nesting level, so roughly 494 levels
|
|
14
|
+
exhausted the default 1000-frame limit — measured, and the same 494 for
|
|
15
|
+
`<div>`, `<p>`, `<blockquote>` and `<table><tr><td>`, 495 for
|
|
16
|
+
`<ul><li>`, which is what identifies the cost as per-level. An
|
|
17
|
+
unclosed tag counts too: `html.parser` does not auto-close `<p>` or
|
|
18
|
+
`<li>`, so `"<p>" * 5000` really is 5000 levels.
|
|
19
|
+
|
|
20
|
+
Because the converter was wired as a Pydantic `BeforeValidator`, the
|
|
21
|
+
failure landed inside `model_validate` — that is, inside `get_ticket` —
|
|
22
|
+
as a bare builtin from a library whose whole error surface is supposed
|
|
23
|
+
to derive from `GlpiError`.
|
|
24
|
+
|
|
25
|
+
`from_transport` now *attempts* the conversion and answers the
|
|
26
|
+
`RecursionError` by stripping the document to its text instead.
|
|
27
|
+
**It degrades, it never truncates, and it does not raise for depth**:
|
|
28
|
+
every character of prose the converting path would have produced also
|
|
29
|
+
appears in the degraded rendering.
|
|
30
|
+
|
|
31
|
+
Attempting it rather than predicting it is the whole design, and it
|
|
32
|
+
replaced a fixed `MAX_HTML_DEPTH = 200` bound that was wrong in both
|
|
33
|
+
directions. Too low, because the budget is not 1000 frames but whatever
|
|
34
|
+
is left of the stack when the conversion starts, and that belongs to
|
|
35
|
+
the caller — so the bound had to assume the worst and flattened every
|
|
36
|
+
body between 200 and the real cliff of about 494. Measured, a
|
|
37
|
+
300-level and a 400-level body now come back as **Markdown with their
|
|
38
|
+
links, emphasis and lists intact** where they used to come back as
|
|
39
|
+
plain text, with no error to notice and no way to ask for better. And
|
|
40
|
+
too fragile, because predicting the depth meant reproducing the
|
|
41
|
+
parser's idea of the tree: three rounds of adversarial review found
|
|
42
|
+
seven ways for that estimate to land *under* the real depth, each of
|
|
43
|
+
which sent a document to `markdownify` and into the very
|
|
44
|
+
`RecursionError` the bound existed to prevent.
|
|
45
|
+
|
|
46
|
+
Trying the conversion cannot be wrong about whether the conversion
|
|
47
|
+
fits. `MAX_HTML_DEPTH` and the scan behind it are gone; the constant
|
|
48
|
+
was introduced in this same unreleased cycle and never shipped.
|
|
49
|
+
|
|
50
|
+
Two consequences worth knowing. The outcome now depends on the caller's
|
|
51
|
+
remaining stack, so the same body can convert from one call site and
|
|
52
|
+
degrade from a deeper one — nothing is lost either way, but a caller
|
|
53
|
+
comparing two renderings of one body should know which knob moved it.
|
|
54
|
+
And a body too deep to convert now pays the failed attempt before it
|
|
55
|
+
degrades: measured, 2.0x to 2.6x the old cost at 600 and 5000 levels.
|
|
56
|
+
Ordinary bodies got *faster*, at 0.87x to 0.90x, because the scan they
|
|
57
|
+
used to pay for on every read is gone.
|
|
58
|
+
|
|
59
|
+
A document `html.parser` refuses outright — `<.
|
|
|
34
389
|
database; without it `zoneinfo` resolves on Linux CI and raises on a
|
|
35
390
|
developer machine.
|
|
36
391
|
|
|
392
|
+
- **`changed_since` no longer assumes UTC.** An aware `datetime` now needs the
|
|
393
|
+
server's timezone and raises `GlpiValidationError` without it:
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
window = changed_since(last_run, tz=client.server_timezone)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The bound this builds is compared against a naive server-local column, so
|
|
400
|
+
converting the caller's moment to UTC asked the server for a different one.
|
|
401
|
+
A 09:33 Paris timestamp became a `07:33` filter. East of UTC that only
|
|
402
|
+
re-reads a few hours on every sweep; west of it the bound moves *forward*
|
|
403
|
+
and modifications are skipped outright — four hours in New York, seven in
|
|
404
|
+
Los Angeles — and the size of the drift changes at each DST transition, so
|
|
405
|
+
a sync that looks correct in January starts losing rows in March.
|
|
406
|
+
|
|
407
|
+
The offset is now spent converting the value onto the server's clock and
|
|
408
|
+
then dropped, which is what the model serialiser already does on the way
|
|
409
|
+
out; the two halves had diverged by exactly the offset. Missing `tz` is
|
|
410
|
+
refused rather than defaulted for the same reason `server_timezone` has no
|
|
411
|
+
default: every guess is wrong somewhere, and being wrong here returns a
|
|
412
|
+
short result set rather than an error.
|
|
413
|
+
|
|
414
|
+
A `date`, an ISO string, or a naive `datetime` is unaffected and needs no
|
|
415
|
+
`tz` — a naive value already means the server's clock.
|
|
416
|
+
|
|
37
417
|
- **Search endpoints now raise on a 4xx instead of returning `[]`.** The seven
|
|
38
418
|
`search_*` helpers passed no `failure_message` to `_resource_list`, which
|
|
39
419
|
skipped the status check entirely, so a 400, 401, 403 or 404 came back as an
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: glpi-python-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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/
|
|
@@ -70,7 +70,11 @@ The goal is to let GLPI integrations work with domain objects instead of raw
|
|
|
70
70
|
JSON payloads. The package exposes Pydantic models for tickets, users,
|
|
71
71
|
followups, documents, locations, and related records, while converting GLPI
|
|
72
72
|
HTML content into Markdown for Python-side workflows and rendering Markdown
|
|
73
|
-
back to HTML for outgoing payloads.
|
|
73
|
+
back to HTML for outgoing payloads. On a response model that conversion is
|
|
74
|
+
lazy — `.content` converts on first read and caches, so listing records
|
|
75
|
+
costs nothing per body — and it degrades to plain text rather than failing
|
|
76
|
+
on pathologically nested HTML. See
|
|
77
|
+
[Rich-text content](https://glpi-python-client.readthedocs.io/en/latest/user_guide.html#content-conversion).
|
|
74
78
|
|
|
75
79
|
It currently focuses on ticket-centric workflows and exposes two high-level
|
|
76
80
|
clients built on top of the GLPI v2 REST API:
|
|
@@ -12,7 +12,11 @@ The goal is to let GLPI integrations work with domain objects instead of raw
|
|
|
12
12
|
JSON payloads. The package exposes Pydantic models for tickets, users,
|
|
13
13
|
followups, documents, locations, and related records, while converting GLPI
|
|
14
14
|
HTML content into Markdown for Python-side workflows and rendering Markdown
|
|
15
|
-
back to HTML for outgoing payloads.
|
|
15
|
+
back to HTML for outgoing payloads. On a response model that conversion is
|
|
16
|
+
lazy — `.content` converts on first read and caches, so listing records
|
|
17
|
+
costs nothing per body — and it degrades to plain text rather than failing
|
|
18
|
+
on pathologically nested HTML. See
|
|
19
|
+
[Rich-text content](https://glpi-python-client.readthedocs.io/en/latest/user_guide.html#content-conversion).
|
|
16
20
|
|
|
17
21
|
It currently focuses on ticket-centric workflows and exposes two high-level
|
|
18
22
|
clients built on top of the GLPI v2 REST API:
|
|
@@ -27,11 +27,13 @@ from it, so neither wraps the other and the two cannot drift apart.
|
|
|
27
27
|
Exceptions
|
|
28
28
|
----------
|
|
29
29
|
|
|
30
|
-
Exceptions raised for a bad argument, an unexpected HTTP status,
|
|
31
|
-
unusable response body derive from
|
|
32
|
-
:class:`GlpiStatusError`, :class:`GlpiValidationError`
|
|
33
|
-
:class:`GlpiProtocolError` also inherit :class:`ValueError` for
|
|
34
|
-
compatibility with releases that raised bare ``ValueError
|
|
30
|
+
Exceptions raised for a bad argument, an unexpected HTTP status, an
|
|
31
|
+
unusable response body, or content that cannot be converted derive from
|
|
32
|
+
:class:`GlpiError`. :class:`GlpiStatusError`, :class:`GlpiValidationError`
|
|
33
|
+
and :class:`GlpiProtocolError` also inherit :class:`ValueError` for
|
|
34
|
+
backwards compatibility with releases that raised bare ``ValueError``;
|
|
35
|
+
:class:`GlpiContentError` and :class:`GlpiTransportError` do not, because
|
|
36
|
+
nothing was passed in wrongly in either case.
|
|
35
37
|
|
|
36
38
|
Network-level faults (connection failures, DNS errors, timeouts) are
|
|
37
39
|
raised as :class:`GlpiTransportError`, or its :class:`GlpiTimeoutError`
|
|
@@ -80,6 +82,55 @@ guide for the full picture, including which methods raise which type.
|
|
|
80
82
|
:members:
|
|
81
83
|
:show-inheritance:
|
|
82
84
|
|
|
85
|
+
.. autoexception:: GlpiContentError
|
|
86
|
+
:members:
|
|
87
|
+
:show-inheritance:
|
|
88
|
+
|
|
89
|
+
Rich-text content
|
|
90
|
+
-----------------
|
|
91
|
+
|
|
92
|
+
GLPI exchanges ticket, followup, task, solution and knowledge-base bodies
|
|
93
|
+
as HTML. The package's surface is Markdown in both directions, but the two
|
|
94
|
+
directions work differently, and the difference is visible.
|
|
95
|
+
|
|
96
|
+
A **write** model (``Post*``, ``Patch*``) takes Markdown in ``content`` and
|
|
97
|
+
renders it to HTML when the request is built. Nothing to think about.
|
|
98
|
+
|
|
99
|
+
A **read** model (``Get*``) keeps two views of the same body:
|
|
100
|
+
|
|
101
|
+
``content_html``
|
|
102
|
+
what GLPI sent, verbatim. Also accepts the wire spelling ``content`` on
|
|
103
|
+
construction.
|
|
104
|
+
|
|
105
|
+
``content``
|
|
106
|
+
the same body as Markdown, converted on the first read and cached.
|
|
107
|
+
:class:`GetKBArticle` has ``description`` / ``description_html`` as
|
|
108
|
+
well.
|
|
109
|
+
|
|
110
|
+
Reading ``.content`` is what a caller wants and what earlier releases
|
|
111
|
+
returned, so no read-side code needs changing. What changed is *when* the
|
|
112
|
+
conversion happens, which buys two things: listing records costs nothing
|
|
113
|
+
per body, and a body that cannot be converted no longer stops the rest of
|
|
114
|
+
its page being read.
|
|
115
|
+
|
|
116
|
+
Very deeply nested HTML is the case worth knowing about.
|
|
117
|
+
``markdownify`` walks the document recursively and runs out of stack at
|
|
118
|
+
around 494 levels of nesting. The converter does not try to predict
|
|
119
|
+
that: it attempts the conversion and, if the walk does not fit, strips
|
|
120
|
+
tags instead. It degrades, it never truncates, and it does not raise:
|
|
121
|
+
every character the normal rendering would have produced still appears.
|
|
122
|
+
What is lost is structure rather than words — link targets and image alt
|
|
123
|
+
text, code fencing and ``<pre>`` indentation, `` `` alignment.
|
|
124
|
+
Because the budget is the stack left when the conversion starts, the
|
|
125
|
+
same body can convert from one call site and degrade from a deeper one.
|
|
126
|
+
Anything else that goes wrong in either direction raises
|
|
127
|
+
:class:`GlpiContentError`.
|
|
128
|
+
|
|
129
|
+
Because the conversion is cached on first read, a read model should be
|
|
130
|
+
treated as immutable afterwards: assigning to ``content_html``, or
|
|
131
|
+
``model_copy(update={"content_html": ...})``, leaves the cached Markdown
|
|
132
|
+
in place. Rebuild through ``model_validate`` if you need to change it.
|
|
133
|
+
|
|
83
134
|
Aggregated Models
|
|
84
135
|
-----------------
|
|
85
136
|
|
|
@@ -82,7 +82,18 @@ python -m pytest
|
|
|
82
82
|
- `glpi_python_client.models` contains typed request and response
|
|
83
83
|
models.
|
|
84
84
|
- `glpi_python_client.content` handles HTML/Markdown conversion for
|
|
85
|
-
ticket descriptions, followups, tasks,
|
|
85
|
+
ticket descriptions, followups, tasks, solutions, knowledge-base
|
|
86
|
+
articles (`content` and `description`) and article revisions. It is
|
|
87
|
+
wired into the models by `models/api_schema/_content.py`, eagerly on
|
|
88
|
+
the write models and through a cached property on the read ones.
|
|
89
|
+
Inbound HTML too deep for `markdownify` to walk is tag-stripped
|
|
90
|
+
rather than parsed. The depth is not predicted: the conversion is
|
|
91
|
+
attempted and the `RecursionError` answered, because the budget is the
|
|
92
|
+
caller's remaining stack and no bound computed in advance can know it.
|
|
93
|
+
The module docstring carries the derivation and the rejected
|
|
94
|
+
alternatives (`sys.setrecursionlimit`, and a thread with a larger
|
|
95
|
+
stack, which needs the same global). The prohibition is enforced by
|
|
96
|
+
`testing/tests/test_raise_site_audit.py`, not just written down.
|
|
86
97
|
- `glpi_python_client.testing` exposes `make_client` and
|
|
87
98
|
`make_async_client` factories that produce in-memory clients with no
|
|
88
99
|
real HTTP plumbing for downstream test suites, plus the shared
|
|
@@ -586,7 +586,11 @@ Knowledge base
|
|
|
586
586
|
The knowledge base mixins map to ``/Knowledgebase``. Articles and
|
|
587
587
|
categories expose the ``search_ / get_ / create_ / update_ / delete_``
|
|
588
588
|
shape; comments are nested under an article; revisions are read-only.
|
|
589
|
-
Article ``content`` and ``description`` accept and return Markdown
|
|
589
|
+
Article ``content`` and ``description`` accept and return Markdown; on
|
|
590
|
+
:class:`~glpi_python_client.GetKBArticle` they are properties over
|
|
591
|
+
``content_html`` and ``description_html``, converted on first read -- see
|
|
592
|
+
:ref:`content-conversion`, which matters here because searching the
|
|
593
|
+
knowledge base returns whole article bodies. An
|
|
590
594
|
article's ``categories`` association is read-only in the v2 GLPI contract,
|
|
591
595
|
so the client sets it through a legacy fallback — see
|
|
592
596
|
`Assigning categories`_.
|
|
@@ -1429,9 +1433,9 @@ Example output::
|
|
|
1429
1433
|
-----------------
|
|
1430
1434
|
|
|
1431
1435
|
Exceptions the client raises for a bad argument, an unexpected HTTP
|
|
1432
|
-
status,
|
|
1433
|
-
:class:`~glpi_python_client.GlpiError`, so one handler covers that
|
|
1434
|
-
of the library surface:
|
|
1436
|
+
status, an unusable response body, or content it cannot convert derive
|
|
1437
|
+
from :class:`~glpi_python_client.GlpiError`, so one handler covers that
|
|
1438
|
+
part of the library surface:
|
|
1435
1439
|
|
|
1436
1440
|
.. code-block:: python
|
|
1437
1441
|
|
|
@@ -1474,15 +1478,17 @@ The hierarchy lets you narrow as far as you need:
|
|
|
1474
1478
|
.. code-block:: text
|
|
1475
1479
|
|
|
1476
1480
|
GlpiError
|
|
1477
|
-
├── GlpiTransportError
|
|
1478
|
-
│ └── GlpiTimeoutError
|
|
1481
|
+
├── GlpiTransportError the request never produced a response
|
|
1482
|
+
│ └── GlpiTimeoutError GLPI was too slow
|
|
1479
1483
|
├── GlpiStatusError GLPI answered with an unexpected status
|
|
1480
1484
|
│ ├── GlpiAuthError 401 / 403
|
|
1481
1485
|
│ ├── GlpiNotFoundError 404
|
|
1482
1486
|
│ └── GlpiServerError 5xx (retried up to 3 attempts before it
|
|
1483
1487
|
│ reaches you)
|
|
1484
1488
|
├── GlpiValidationError the client rejected your argument
|
|
1485
|
-
|
|
1489
|
+
├── GlpiProtocolError GLPI answered 2xx with an unusable body
|
|
1490
|
+
└── GlpiContentError a rich-text body could not be converted
|
|
1491
|
+
between HTML and Markdown
|
|
1486
1492
|
|
|
1487
1493
|
:class:`~glpi_python_client.GlpiStatusError` carries the diagnostics you
|
|
1488
1494
|
usually want:
|
|
@@ -1505,6 +1511,73 @@ usually want:
|
|
|
1505
1511
|
:class:`~glpi_python_client.GlpiProtocolError` also inherit
|
|
1506
1512
|
:class:`ValueError`. Code written against earlier releases, which
|
|
1507
1513
|
raised bare ``ValueError``, keeps working unchanged.
|
|
1514
|
+
:class:`~glpi_python_client.GlpiContentError` and
|
|
1515
|
+
:class:`~glpi_python_client.GlpiTransportError` do not inherit it:
|
|
1516
|
+
there was never a bare ``ValueError`` at either kind of site, and
|
|
1517
|
+
neither is a value the caller got wrong.
|
|
1518
|
+
|
|
1519
|
+
.. _content-conversion:
|
|
1520
|
+
|
|
1521
|
+
Rich-text content: Markdown in, Markdown out
|
|
1522
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
1523
|
+
|
|
1524
|
+
Ticket, followup, task, solution and knowledge-base bodies travel to GLPI
|
|
1525
|
+
as HTML. You work in Markdown in both directions and the package handles
|
|
1526
|
+
the translation, but the two directions are not symmetric and the
|
|
1527
|
+
difference shows up in the field names.
|
|
1528
|
+
|
|
1529
|
+
Writing is the simple half: give ``content`` Markdown and it is rendered
|
|
1530
|
+
to HTML when the request is built.
|
|
1531
|
+
|
|
1532
|
+
Reading gives you two views of the same body:
|
|
1533
|
+
|
|
1534
|
+
.. code-block:: python
|
|
1535
|
+
|
|
1536
|
+
ticket = client.get_ticket(42)
|
|
1537
|
+
|
|
1538
|
+
ticket.content_html # '<p>The printer is <strong>offline</strong>.</p>'
|
|
1539
|
+
ticket.content # 'The printer is **offline**.'
|
|
1540
|
+
|
|
1541
|
+
``.content`` is what you want and what earlier releases gave you, so
|
|
1542
|
+
read-side code needs no change. What changed is *when* the conversion
|
|
1543
|
+
runs: on the first read of ``.content``, cached afterwards, rather than
|
|
1544
|
+
while the model is being built. Two things follow.
|
|
1545
|
+
|
|
1546
|
+
Listing records is cheap. ``client.search_tickets()`` used to convert
|
|
1547
|
+
every body on the page whether or not you looked at one; now a search
|
|
1548
|
+
that only reads ``id`` and ``date_mod`` converts nothing at all.
|
|
1549
|
+
|
|
1550
|
+
A body that cannot be converted no longer takes its page down with it.
|
|
1551
|
+
The whole page is built in one pass, so a single unconvertible record used
|
|
1552
|
+
to make its page-mates unreadable too. The failure is now scoped to the
|
|
1553
|
+
record whose body you actually read.
|
|
1554
|
+
|
|
1555
|
+
.. note::
|
|
1556
|
+
|
|
1557
|
+
Deeply nested HTML is the case worth knowing about. The HTML-to-Markdown
|
|
1558
|
+
converter walks the document recursively and exhausts the interpreter's
|
|
1559
|
+
stack at around 494 levels of nesting. ``.content`` does not try to
|
|
1560
|
+
predict that -- it attempts the conversion and, when the walk does not
|
|
1561
|
+
fit, strips the tags instead. **It degrades, it never truncates, and it
|
|
1562
|
+
does not raise**: every character the normal rendering would have
|
|
1563
|
+
produced still appears, so a body never says less because of how deeply
|
|
1564
|
+
it happened to nest. What you lose is structure,
|
|
1565
|
+
not words — link targets and image alt text, code-block fencing and
|
|
1566
|
+
``<pre>`` indentation, and `` ``-padded alignment. Anything else
|
|
1567
|
+
that goes wrong raises
|
|
1568
|
+
:class:`~glpi_python_client.GlpiContentError`.
|
|
1569
|
+
|
|
1570
|
+
Because the result is cached on first read, treat a read model as
|
|
1571
|
+
immutable afterwards. Assigning to ``content_html`` -- or
|
|
1572
|
+
``model_copy(update={"content_html": ...})`` -- leaves the cached
|
|
1573
|
+
Markdown in place, and nothing in ``repr``, ``==`` or ``model_dump``
|
|
1574
|
+
will tell you. Rebuild through ``model_validate`` instead.
|
|
1575
|
+
|
|
1576
|
+
Two smaller consequences, if you are upgrading from 0.4.x: ``content`` is
|
|
1577
|
+
no longer in ``GetTicket.model_fields``, and ``GetTicket(...).model_dump()``
|
|
1578
|
+
emits ``content_html`` holding HTML where it used to emit ``content``
|
|
1579
|
+
holding Markdown. Pass ``by_alias=True`` for a dump keyed the way GLPI
|
|
1580
|
+
keys it.
|
|
1508
1581
|
|
|
1509
1582
|
Retry behaviour
|
|
1510
1583
|
~~~~~~~~~~~~~~~
|
|
@@ -1533,4 +1606,4 @@ most 2 POST requests.
|
|
|
1533
1606
|
|
|
1534
1607
|
Search methods are deliberately tolerant: ``search_tickets`` and its
|
|
1535
1608
|
siblings return an empty list rather than raising when GLPI rejects the
|
|
1536
|
-
query. Methods that fetch or mutate one specific record always raise.
|
|
1609
|
+
query. Methods that fetch or mutate one specific record always raise.
|