glpi-python-client 0.6.0__tar.gz → 0.6.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.6.0 → glpi_python_client-0.6.1}/CHANGELOG.md +128 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/PKG-INFO +4 -4
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/api_reference.rst +12 -6
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/development.md +7 -3
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/installation.rst +6 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/user_guide.rst +231 -19
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/__init__.py +1 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_errors.py +2 -2
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/content/conversion.py +179 -22
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/_content.py +13 -2
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/pyproject.toml +21 -5
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-asset-workflow/SKILL.md +1 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-client-setup/SKILL.md +1 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-contract-workflow/SKILL.md +1 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-document-workflow/SKILL.md +1 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-knowledge-base/SKILL.md +2 -2
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-plugin-fields/SKILL.md +2 -2
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-reporting-and-context/SKILL.md +1 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-team-members/SKILL.md +1 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-ticket-timeline/SKILL.md +3 -3
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-ticket-workflow/SKILL.md +3 -3
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/glpi-user-location-provisioning/SKILL.md +1 -1
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/.gitignore +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/.pre-commit-config.yaml +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/.readthedocs.yaml +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/CONTRIBUTING.md +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/LICENSE +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/README.md +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/_static/.gitkeep +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/conf.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/development_rtd.rst +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/index.rst +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/publishing.md +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/publishing_rtd.rst +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/docs/sponsoring.rst +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/_concurrency.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/_testing.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/auth/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/auth/_v1_session.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/auth/auth.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/_base_client.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/administration/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/administration/_entity.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/administration/_user.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assets/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assets/_computer.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assistance/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assistance/_team.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assistance/_ticket.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/dropdowns/_contract_type.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/dropdowns/_location.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/knowledgebase/_article.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/knowledgebase/_revision.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/management/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/management/_contract.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/management/_document.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/plugins/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/api/plugins/_fields.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/client.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/commons/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/commons/_config.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/commons/_constants.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/commons/_filters.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/commons/_http.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/commons/_payloads.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/commons/_transport.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/custom/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/custom/_statistics.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_async/clients/custom/_ticket_context.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/_concurrency.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/_testing.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/auth/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/auth/_v1_session.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/auth/auth.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/_base_client.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/administration/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/administration/_entity.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/administration/_user.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assets/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assets/_computer.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assistance/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assistance/_team.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assistance/_ticket.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/dropdowns/_contract_type.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/dropdowns/_location.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/knowledgebase/_article.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/knowledgebase/_revision.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/management/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/management/_contract.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/management/_document.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/plugins/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/api/plugins/_fields.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/client.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/commons/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/commons/_config.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/commons/_constants.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/commons/_filters.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/commons/_http.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/commons/_payloads.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/commons/_transport.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/custom/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/custom/_statistics.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/_sync/clients/custom/_ticket_context.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/content/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/_base.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/_common.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/administration/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/administration/_entity.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/administration/_user.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assets/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assets/_computer.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assets/_contract_item.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assistance/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assistance/_team.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assistance/_ticket.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/dropdowns/_contract_type.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/dropdowns/_location.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/enums.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/knowledgebase/_article.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/knowledgebase/_revision.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/management/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/management/_contract.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/management/_contract_cost.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/management/_document.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/plugins/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/api_schema/plugins/_fields.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/custom_schema/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/models/custom_schema/_ticket_context.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/py.typed +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/rsql.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/testing/__init__.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/testing/fixtures.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/glpi_python_client/testing/utils.py +0 -0
- {glpi_python_client-0.6.0 → glpi_python_client-0.6.1}/skills/README.md +0 -0
|
@@ -4,6 +4,134 @@ 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.6.1 — 2026-10-02
|
|
8
|
+
|
|
9
|
+
The reader now gives the same Markdown as `easyvista-python-client` 0.4.1,
|
|
10
|
+
whose converter is this package's 0.6.0 converter plus fifteen fixes and a
|
|
11
|
+
correction to one of them. This release ports those fixes and the
|
|
12
|
+
correction, with their tests. The two conversion modules now differ only in
|
|
13
|
+
names, error messages, docstrings and that package's optional-dependency
|
|
14
|
+
import guard. The converter's writing (`to_transport`) is unchanged; a
|
|
15
|
+
write model now sends a lone surrogate as U+FFFD.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Image alt text lost its escapes**: `a_b_c.png` read back as `abc.png`.
|
|
20
|
+
Escapes and character references in alt text are kept.
|
|
21
|
+
- **A line opening with `~~~` became a code fence** that swallowed the rest
|
|
22
|
+
of the body. It reads as `\~~~`.
|
|
23
|
+
- **A `!` right before a link turned the link into an image**
|
|
24
|
+
(`Attention!` then a link). It reads as `\!`.
|
|
25
|
+
- **An image whose alt text starts with `^` stopped being an image** once
|
|
26
|
+
rendered, since cmark-gfm never opens one on `![^`. The `^` is escaped.
|
|
27
|
+
- **`<<a@example.org>` became an autolink** behind an escaped `<`. The second
|
|
28
|
+
`<` is escaped too.
|
|
29
|
+
- **A line break inside inline code showed as a literal backslash.**
|
|
30
|
+
`<code>`, `<kbd>` and `<samp>` holding a `<br>` become one span per line;
|
|
31
|
+
a `|` in `<kbd>` or `<samp>` in a table cell no longer splits the row.
|
|
32
|
+
- **A table inside a heading or a link showed its pipes as text**, and
|
|
33
|
+
inside a link did not read back as itself. It is written as its cells'
|
|
34
|
+
text, as a table inside a cell already was.
|
|
35
|
+
- **A block inside such a flattened table broke its holder's line.** It
|
|
36
|
+
stays on that line.
|
|
37
|
+
- **Bold ending in punctuation at a flattened cell's edge came back as raw
|
|
38
|
+
`<strong>`**, which read back as `**`. It stays Markdown bold.
|
|
39
|
+
- **Two `<center>` blocks ran together.** `<center>` is a block, as `<div>`
|
|
40
|
+
is, except inside `<pre>`.
|
|
41
|
+
- **Underline, highlight and inserted text lost their formatting.** `<u>`,
|
|
42
|
+
`<mark>` and `<ins>` are kept as raw tags, as `<s>` already was. Round a
|
|
43
|
+
block (a table, a list, a heading, a quote, a code block, a rule or
|
|
44
|
+
paragraphs) the tag is dropped and the blocks read as 0.6.0 read them.
|
|
45
|
+
Markdown has no inline tag round blocks: `easyvista-python-client` 0.4.0,
|
|
46
|
+
which kept it, showed a table as pipe text and a list or a heading as its
|
|
47
|
+
Markdown source, and round a `<pre>` left a fence open to the end of the
|
|
48
|
+
body. Inside a table cell or a heading, whose blocks are one line, the tag
|
|
49
|
+
stays.
|
|
50
|
+
- **A long ordered list was quadratic to read**: 5,000 items took 3.1 s,
|
|
51
|
+
and take 0.6 s now (CPython 3.12.3). An `<ol start>` such as `²` or `½`
|
|
52
|
+
counts from 1, as a browser counts a start holding no digit, instead of
|
|
53
|
+
failing the body.
|
|
54
|
+
- **A run of spaces or line breaks at the edge of bold, italic or a link was
|
|
55
|
+
quadratic to read.** It is linear.
|
|
56
|
+
- **A `colspan` or `start` markdownify cannot read as a number raised
|
|
57
|
+
`GlpiContentError`** from `.content` (`colspan="²"`, or 5,000 digits). The
|
|
58
|
+
body degrades to its text instead, every word kept. Any other
|
|
59
|
+
`ValueError` raised while converting takes the same path.
|
|
60
|
+
- **A lone surrogate in a write model's Markdown failed the whole write**
|
|
61
|
+
with `GlpiContentError`: cmark-gfm renders UTF-8, which cannot encode
|
|
62
|
+
one. Each lone surrogate is written as U+FFFD instead, as CommonMark
|
|
63
|
+
replaces a NUL, and the rest of the body keeps its Markdown. Calling
|
|
64
|
+
`GlpiContentConverter.to_transport` directly still raises, as
|
|
65
|
+
`easyvista-python-client`'s converter does.
|
|
66
|
+
- **A run of unfinished tags at the end of a body was quadratic to read** on
|
|
67
|
+
CPython before 3.11.14, 3.12.12 and 3.13.6 (CVE-2025-6069). Every `<`
|
|
68
|
+
after the body's last `>` is read as text, so `x <a b` at the very end
|
|
69
|
+
reads as `x \<a b` on every interpreter, where a patched one dropped it.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **`.content` changes once** for a body holding any shape above, so stored
|
|
74
|
+
digests of such bodies change once.
|
|
75
|
+
- **Deeply nested HTML degrades to text sooner.** The reader spends one more
|
|
76
|
+
stack frame per level: from a shallow stack, the deepest `<div>` document
|
|
77
|
+
read with its structure went from about 490 levels to about 326 (CPython
|
|
78
|
+
3.12.3 and 3.13.14, default recursion limit). `<blockquote>` stays at
|
|
79
|
+
about 194. The words are kept either way.
|
|
80
|
+
- **Dependency bounds** are now those of `easyvista-python-client[content]`
|
|
81
|
+
0.4.0, so the two packages resolve to the same libraries side by side:
|
|
82
|
+
`markdown-it-py>=3.0,<4` (the alt-text fix relies on 3.x's `text_special`
|
|
83
|
+
tokens, and `mdformat` 0.7.22 requires `<4` anyway),
|
|
84
|
+
`markdownify>=1.2.3,<1.3` and `mdformat-tables>=1.0,<1.1`.
|
|
85
|
+
`mdformat>=0.7.22,<0.8` is unchanged. The reader overrides private
|
|
86
|
+
surfaces of `markdownify` and `mdformat`; `pyproject.toml` gives the
|
|
87
|
+
reason for each bound.
|
|
88
|
+
|
|
89
|
+
### Documentation
|
|
90
|
+
|
|
91
|
+
- The user guide's rich-text section now says what each element becomes,
|
|
92
|
+
that neither direction sanitises, what survives a round trip, and the
|
|
93
|
+
known holes, all reproduced on 2026-10-02. Its claim that a read always
|
|
94
|
+
displays the same and reads back as itself is now stated as the aim it
|
|
95
|
+
is, as in the API reference and the module docstring.
|
|
96
|
+
- The deep-nesting note gives the measured depths, and that a read started
|
|
97
|
+
within about 30 frames of the recursion limit can still raise.
|
|
98
|
+
- `GlpiContentError`'s docstring named python-markdown as the writer; it is
|
|
99
|
+
cmark-gfm.
|
|
100
|
+
- The `glpi-ticket-timeline`, `glpi-ticket-workflow` and
|
|
101
|
+
`glpi-knowledge-base` skills describe raw `<u>`, `<mark>`, `<ins>` and
|
|
102
|
+
`<s>`, the write-model rule exactly, and the new depth.
|
|
103
|
+
- Corrected after review: a write model keeps the caller's Markdown
|
|
104
|
+
stripped at both ends, not verbatim; the `glpi-knowledge-base` and
|
|
105
|
+
`glpi-plugin-fields` skills no longer say a deep body never raises; any
|
|
106
|
+
`ValueError` degrades to text, not only a `colspan` or `start`; a
|
|
107
|
+
`start` counts as a browser counts it only when it holds no digit; and
|
|
108
|
+
the CVE-2025-6069 note says that everything after a body's last `>`,
|
|
109
|
+
an unterminated comment or a cut-short closing tag included, is shown.
|
|
110
|
+
|
|
111
|
+
### Tests
|
|
112
|
+
|
|
113
|
+
- `content/tests/test_fixes.py` pins each of the fifteen fixes; run against
|
|
114
|
+
0.6.0, 36 of the 54 tests first ported fail on CPython 3.12.3 and 37 on
|
|
115
|
+
3.13.14, the rest being controls and pins. Section 11 also pins the
|
|
116
|
+
correction to fix 11 with 42 tests, 40 of which fail on
|
|
117
|
+
`easyvista-python-client` 0.4.0's converter. Reverting any one fix alone fails at least
|
|
118
|
+
one test of the content suite on both.
|
|
119
|
+
- `content/tests/test_properties.py` holds seeded generators to three
|
|
120
|
+
properties: a body displays what its HTML displayed, is a fixed point,
|
|
121
|
+
and keeps its words in order. Against 0.6.0 all 16 tests fail.
|
|
122
|
+
- `content/tests/test_cost.py` replaces the 20-second budgets on long bodies
|
|
123
|
+
with growth tests, `time(4n) / time(n) < 8`, which a quadratic pass fails
|
|
124
|
+
at a few thousand items.
|
|
125
|
+
- Four guards that no test caught are pinned, each by a test that fails
|
|
126
|
+
without it: a second `<` before a space keeps one escape, a header cell
|
|
127
|
+
escapes a `|` in `<kbd>` or `<samp>` and splits a line break in inline
|
|
128
|
+
code, a table inside an `<a>` without `href` stays a table, and an ordered
|
|
129
|
+
item numbered 10 or more indents its content by its bullet's width. A
|
|
130
|
+
count pins that the walk finding blocks inside `<u>`, `<mark>` and `<ins>`
|
|
131
|
+
checks each tag about once.
|
|
132
|
+
- The display oracle treats a flattened cell's edge as a word boundary, as a
|
|
133
|
+
browser does, and reads `start` with `isdecimal`.
|
|
134
|
+
|
|
7
135
|
## 0.6.0 — 2026-10-01
|
|
8
136
|
|
|
9
137
|
### Changed (breaking)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: glpi-python-client
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.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/
|
|
@@ -27,9 +27,9 @@ Requires-Dist: beautifulsoup4>=4.15
|
|
|
27
27
|
Requires-Dist: cmarkgfm>=2025.10
|
|
28
28
|
Requires-Dist: httpx>=0.28
|
|
29
29
|
Requires-Dist: lxml>=4.9
|
|
30
|
-
Requires-Dist: markdown-it-py
|
|
31
|
-
Requires-Dist: markdownify
|
|
32
|
-
Requires-Dist: mdformat-tables
|
|
30
|
+
Requires-Dist: markdown-it-py<4,>=3.0
|
|
31
|
+
Requires-Dist: markdownify<1.3,>=1.2.3
|
|
32
|
+
Requires-Dist: mdformat-tables<1.1,>=1.0
|
|
33
33
|
Requires-Dist: mdformat<0.8,>=0.7.22
|
|
34
34
|
Requires-Dist: pydantic>=2.8
|
|
35
35
|
Requires-Dist: sniffio>=1.3
|
|
@@ -115,17 +115,23 @@ its page being read.
|
|
|
115
115
|
|
|
116
116
|
The Markdown is CommonMark with GFM tables, rendered by cmark-gfm. It
|
|
117
117
|
spells a body's text as literal text -- ``\_\_init\_\_``, ``\\\serveur``,
|
|
118
|
-
``\# pas un titre`` -- so rendering it displays what GLPI displayed and
|
|
119
|
-
reading that back gives the same Markdown.
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
``\# pas un titre`` -- so that rendering it displays what GLPI displayed and
|
|
119
|
+
reading that back gives the same Markdown. That is the aim; the user guide
|
|
120
|
+
lists the shapes where it falls short. Underline, highlight and struck text
|
|
121
|
+
stay as raw ``<u>``, ``<mark>``, ``<ins>`` and ``<s>``. Neither direction
|
|
122
|
+
sanitises: raw HTML and ``javascript:`` links pass through. A write model
|
|
123
|
+
keeps the caller's Markdown as written, stripped at both ends, unless it
|
|
124
|
+
starts with ``<`` and holds an HTML element, and sends a lone surrogate as
|
|
125
|
+
U+FFFD. See :ref:`content-conversion`.
|
|
122
126
|
|
|
123
127
|
Very deeply nested HTML is the case worth knowing about.
|
|
124
128
|
``markdownify`` walks the document recursively and runs out of stack at
|
|
125
129
|
a few hundred levels of nesting. The converter attempts the conversion
|
|
126
130
|
and, if the walk does not fit, reads the body's text instead, a line per
|
|
127
|
-
block: the words survive, the structure does not.
|
|
128
|
-
|
|
131
|
+
block: the words survive, the structure does not. Any ``ValueError`` from
|
|
132
|
+
the conversion degrades the same way: markdownify raises one for a
|
|
133
|
+
``colspan`` or ``start`` it cannot read as a number. Anything else that
|
|
134
|
+
goes wrong in either direction raises :class:`GlpiContentError`.
|
|
129
135
|
|
|
130
136
|
Because the conversion is cached on first read, a read model should be
|
|
131
137
|
treated as immutable afterwards: assigning to ``content_html``, or
|
|
@@ -86,13 +86,17 @@ python -m pytest
|
|
|
86
86
|
articles (`content` and `description`) and article revisions. It is
|
|
87
87
|
wired into the models by `models/api_schema/_content.py`, eagerly on
|
|
88
88
|
the write models and through a cached property on the read ones.
|
|
89
|
-
Inbound HTML too deep for `markdownify` to walk
|
|
89
|
+
Inbound HTML too deep for `markdownify` to walk, or on which it
|
|
90
|
+
raises `ValueError` (a `colspan` or `start` it cannot read as a
|
|
91
|
+
number, among others), is tag-stripped
|
|
90
92
|
rather than parsed. The depth is not predicted: the conversion is
|
|
91
93
|
attempted and the `RecursionError` answered, because the budget is the
|
|
92
94
|
caller's remaining stack and no bound computed in advance can know it.
|
|
93
|
-
The
|
|
95
|
+
The 0.5.0 changelog entry carries the derivation and the rejected
|
|
94
96
|
alternatives (`sys.setrecursionlimit`, and a thread with a larger
|
|
95
|
-
stack, which needs the same global). The
|
|
97
|
+
stack, which needs the same global). The reader is kept identical, but
|
|
98
|
+
for names, messages, docstrings and an import guard, to
|
|
99
|
+
`easyvista-python-client`'s; change the two together. The prohibition is enforced by
|
|
96
100
|
`testing/tests/test_raise_site_audit.py`, not just written down.
|
|
97
101
|
- `glpi_python_client.testing` exposes `make_client` and
|
|
98
102
|
`make_async_client` factories that produce in-memory clients with no
|
|
@@ -6,7 +6,12 @@ Requirements
|
|
|
6
6
|
|
|
7
7
|
``glpi-python-client`` supports Python 3.11 and newer. Runtime dependencies are installed
|
|
8
8
|
from the package metadata and include ``httpx``, ``tenacity``,
|
|
9
|
-
``beautifulsoup4``, ``lxml``, and ``pydantic
|
|
9
|
+
``beautifulsoup4``, ``lxml``, and ``pydantic``, plus the libraries the
|
|
10
|
+
rich-text converter is built on: ``markdownify``, ``mdformat``,
|
|
11
|
+
``mdformat-tables`` and ``markdown-it-py`` to read, and ``cmarkgfm`` to
|
|
12
|
+
write. The reader overrides private surfaces of ``markdownify`` and
|
|
13
|
+
``mdformat``, so those four carry upper bounds; ``pyproject.toml`` gives the
|
|
14
|
+
reason for each.
|
|
10
15
|
|
|
11
16
|
Install from PyPI
|
|
12
17
|
-----------------
|
|
@@ -1758,11 +1758,13 @@ The whole page is built in one pass, so a single unconvertible record used
|
|
|
1758
1758
|
to make its page-mates unreadable too. The failure is now scoped to the
|
|
1759
1759
|
record whose body you actually read.
|
|
1760
1760
|
|
|
1761
|
-
The Markdown is CommonMark with GFM tables.
|
|
1762
|
-
does on the way back to GLPI, with cmark-gfm -- displays what
|
|
1763
|
-
displayed, and reading that rendering back gives the same
|
|
1764
|
-
|
|
1765
|
-
|
|
1761
|
+
The Markdown is CommonMark with GFM tables. The aim is that rendering it --
|
|
1762
|
+
as the package does on the way back to GLPI, with cmark-gfm -- displays what
|
|
1763
|
+
GLPI displayed, and that reading that rendering back gives the same
|
|
1764
|
+
Markdown. It is an aim, not a guarantee for every body: `What survives a
|
|
1765
|
+
round trip`_ lists where it falls short. Text in a body is literal: Markdown
|
|
1766
|
+
has one spelling for ``__init__`` typed by a user and for bold ``init``, so
|
|
1767
|
+
text that would read as syntax is escaped:
|
|
1766
1768
|
|
|
1767
1769
|
.. code-block:: python
|
|
1768
1770
|
|
|
@@ -1781,28 +1783,106 @@ back as ``\`` and a newline, a nested list is indented by its bullet's width,
|
|
|
1781
1783
|
and a table comes back unpadded. A body with no HTML element is plain text
|
|
1782
1784
|
and is read as GLPI shows it, its lines as lines.
|
|
1783
1785
|
|
|
1786
|
+
More of what GLPI displays, and the Markdown it reads as:
|
|
1787
|
+
|
|
1788
|
+
.. code-block:: text
|
|
1789
|
+
|
|
1790
|
+
GLPI displays Markdown
|
|
1791
|
+
---------------------------- ----------------------------
|
|
1792
|
+
__init__ \_\_init\_\_
|
|
1793
|
+
*important* \*important\*
|
|
1794
|
+
# 4521 (at a line start) \# 4521
|
|
1795
|
+
1. pas une liste 1\. pas une liste
|
|
1796
|
+
- pas une liste \- pas une liste
|
|
1797
|
+
> pas une citation \> pas une citation
|
|
1798
|
+
[lien](x) \[lien\](x)
|
|
1799
|
+
<Entrée> \<Entrée>
|
|
1800
|
+
~~~ (at a line start) \~~~
|
|
1801
|
+
Attention! then a link Attention\
|
|
1802
|
+
|
|
1803
|
+
Text GLPI displays as markup is read back as that text, so ``<b>``
|
|
1804
|
+
reads as ``\<b>``, never as a live ``<b>``. What each kind of element
|
|
1805
|
+
becomes:
|
|
1806
|
+
|
|
1807
|
+
* **Bold and italic** become ``**`` and ``*``, or raw ``<strong>`` and
|
|
1808
|
+
``<em>`` where CommonMark would not close the markers, for example bold
|
|
1809
|
+
that ends in punctuation directly followed by a letter:
|
|
1810
|
+
``prix:<b>(10)</b>euros`` reads as ``prix:<strong>(10)</strong>euros``.
|
|
1811
|
+
* **Underline, highlight and inserted text** (``<u>``, ``<mark>``,
|
|
1812
|
+
``<ins>``) stay as those raw tags, and **struck text** (``<s>``,
|
|
1813
|
+
``<del>``, ``<strike>``) as a raw ``<s>``. CommonMark has no spelling for
|
|
1814
|
+
any of them, and writing passes the tags through, so the formatting
|
|
1815
|
+
survives. The cost is raw HTML in the Markdown. Markdown has no inline tag
|
|
1816
|
+
round blocks, so a ``<u>``, ``<mark>`` or ``<ins>`` holding a table, a
|
|
1817
|
+
list, a heading, a quote, a ``<pre>``, a rule or paragraphs is dropped and
|
|
1818
|
+
its blocks kept; inside a table cell or a heading, which hold one line, it
|
|
1819
|
+
stays.
|
|
1820
|
+
* **Links** become ``[text](https://... "title")``, and a link whose text is
|
|
1821
|
+
its own URL, a pasted link, becomes the autolink ``<https://...>``. No
|
|
1822
|
+
link target is filtered, ``javascript:`` included (see `It is not a
|
|
1823
|
+
sanitiser`_). An **image** becomes ````, its alt text
|
|
1824
|
+
escaped like any other text.
|
|
1825
|
+
* **Lists** nest and keep their numbers, including an ``<ol start>``. A
|
|
1826
|
+
``start`` that is not a decimal number counts from 1, as a browser counts
|
|
1827
|
+
one holding no digit, such as ``²``; a browser reads ``" 3"``, ``"+3"``
|
|
1828
|
+
or ``"3abc"`` as 3. **Tables** become GFM tables, and a ``<br>`` inside a cell
|
|
1829
|
+
stays a raw ``<br>``, since a GFM cell is one line. A table inside a cell,
|
|
1830
|
+
a heading or a link is written as its cells' text, spaced apart.
|
|
1831
|
+
* **Preformatted blocks** become fences that keep the ``language-`` class
|
|
1832
|
+
cmark-gfm writes, with a fence longer than any run of backticks in the
|
|
1833
|
+
code. **Inline code** holding a line break becomes one code span per line.
|
|
1834
|
+
* ``<div>`` and ``<center>`` are blocks. ``<head>``, ``<script>``,
|
|
1835
|
+
``<style>``, ``<template>`` and ``<title>`` are dropped, as a browser does
|
|
1836
|
+
not display them. Styling such as ``<font>`` colours or ``<span style>``
|
|
1837
|
+
keeps its text and loses the style.
|
|
1838
|
+
|
|
1784
1839
|
Writing, your Markdown is rendered by cmark-gfm. A newline is a line break,
|
|
1785
1840
|
GFM tables work, and raw HTML passes through, so put a placeholder such as
|
|
1786
|
-
``<login>`` in backticks. A write model keeps your Markdown
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1841
|
+
``<login>`` in backticks. A write model keeps your Markdown as written,
|
|
1842
|
+
stripped at both ends, unless it starts with ``<`` and holds an HTML
|
|
1843
|
+
element anywhere, in which case it is read as HTML. Stripping unindents the
|
|
1844
|
+
first line of a body that opens with an indented code block, which then
|
|
1845
|
+
reads as a paragraph, so open such a body with a fence. Read as HTML,
|
|
1846
|
+
Markdown that opens with an autolink and carries an inline ``<br>`` further
|
|
1847
|
+
on loses that autolink, so start such a value with something else. A
|
|
1848
|
+
Markdown table needs a header row, so a header-less HTML table reads back
|
|
1849
|
+
with an empty one, and struck-through text stays as raw ``<s>``. A lone
|
|
1850
|
+
UTF-16 surrogate in a write model's Markdown, which UTF-8 cannot encode, is
|
|
1851
|
+
sent as U+FFFD; ``GlpiContentConverter.to_transport`` called directly raises
|
|
1852
|
+
on one.
|
|
1790
1853
|
|
|
1791
1854
|
.. note::
|
|
1792
1855
|
|
|
1793
1856
|
Deeply nested HTML is the case worth knowing about. The HTML-to-Markdown
|
|
1794
|
-
converter walks the document recursively
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1857
|
+
converter walks the document recursively, so a deeply nested body can
|
|
1858
|
+
exhaust the interpreter's stack. From a shallow stack, the deepest
|
|
1859
|
+
``<div>`` document that still converts with its structure is about 326
|
|
1860
|
+
levels, and the deepest ``<blockquote>`` one about 194 (measured
|
|
1861
|
+
2026-10-02 on CPython 3.12.3 and 3.13.14 with the default recursion
|
|
1862
|
+
limit; 0.6.0 reached about 490 ``<div>`` levels, and 0.6.1's reader
|
|
1863
|
+
spends one more frame per level). ``.content`` does not try to predict
|
|
1864
|
+
that -- it attempts the conversion and, when the walk does not fit,
|
|
1865
|
+
strips the tags instead. **It degrades and never truncates**: every
|
|
1866
|
+
character the normal rendering would have produced still appears, so a
|
|
1867
|
+
body never says less because of how deeply it happened to nest. What you
|
|
1868
|
+
lose is structure, not words — link targets and image alt text,
|
|
1869
|
+
code-block fencing and ``<pre>`` indentation, and `` ``-padded
|
|
1870
|
+
alignment. Any ``ValueError`` from the conversion takes the same path:
|
|
1871
|
+
markdownify raises one for a ``colspan`` or ``start`` it cannot read as
|
|
1872
|
+
a number, such as a ``colspan`` of ``"²"`` or a value of 5,000 digits.
|
|
1873
|
+
So does a document ``html.parser`` refuses outright. Anything else that
|
|
1874
|
+
goes wrong raises
|
|
1804
1875
|
:class:`~glpi_python_client.GlpiContentError`.
|
|
1805
1876
|
|
|
1877
|
+
Because the budget is whatever stack is left when the call starts, the
|
|
1878
|
+
same body can convert from one call site and degrade from a deeper one,
|
|
1879
|
+
and a caller already close to the recursion limit can get an error after
|
|
1880
|
+
all. Measured 2026-10-02 with the default limit of 1000: an ordinary body
|
|
1881
|
+
converted when read from up to about 969 frames deep and degraded to text
|
|
1882
|
+
from about 970. From about 975 frames it raised
|
|
1883
|
+
:class:`~glpi_python_client.GlpiContentError`, and from about 994 a bare
|
|
1884
|
+
:class:`RecursionError` escaped.
|
|
1885
|
+
|
|
1806
1886
|
Because the result is cached on first read, treat a read model as
|
|
1807
1887
|
immutable afterwards. Assigning to ``content_html`` -- or
|
|
1808
1888
|
``model_copy(update={"content_html": ...})`` -- leaves the cached
|
|
@@ -1815,6 +1895,138 @@ emits ``content_html`` holding HTML where it used to emit ``content``
|
|
|
1815
1895
|
holding Markdown. Pass ``by_alias=True`` for a dump keyed the way GLPI
|
|
1816
1896
|
keys it.
|
|
1817
1897
|
|
|
1898
|
+
**CVE-2025-6069.** CPython's ``html.parser`` before 3.11.14, 3.12.12 and
|
|
1899
|
+
3.13.6 takes quadratic time on some unfinished markup
|
|
1900
|
+
(`python/cpython#135462 <https://github.com/python/cpython/issues/135462>`_).
|
|
1901
|
+
Many unfinished tags after a body's last ``>`` is the shape that reaches the
|
|
1902
|
+
converter, and a body is outside data, so reading spells every ``<`` after
|
|
1903
|
+
the last ``>`` as ``<`` before parsing: no ``<`` there can finish a tag.
|
|
1904
|
+
As a side effect, whatever follows the body's last ``>`` reads as text on
|
|
1905
|
+
every interpreter, where a patched CPython drops some of it: an unfinished
|
|
1906
|
+
tag at the very end, ``x <a``, reads as ``x \<a``; an unterminated
|
|
1907
|
+
comment, ``<!-- note``, as ``\<!-- note``; and a closing tag cut short
|
|
1908
|
+
inside a link, ``<a href="...">lien</a``, leaves ``\</a`` in the link's
|
|
1909
|
+
text.
|
|
1910
|
+
Prefer a patched interpreter anyway: the guard covers the converter's input,
|
|
1911
|
+
and the CPython fix covers the parser itself.
|
|
1912
|
+
|
|
1913
|
+
It is not a sanitiser
|
|
1914
|
+
^^^^^^^^^^^^^^^^^^^^^
|
|
1915
|
+
|
|
1916
|
+
Spelling literal text as text is not sanitising. Neither direction
|
|
1917
|
+
neutralises anything, by design: the Markdown is the caller's own, and
|
|
1918
|
+
cmark-gfm runs with its ``UNSAFE`` option, which is what lets ``<u>`` or a
|
|
1919
|
+
``<br>`` in a table cell through. So:
|
|
1920
|
+
|
|
1921
|
+
* raw HTML in the Markdown is rendered verbatim, and
|
|
1922
|
+
``<script>alert(1)</script>`` goes out as a live ``<script>``;
|
|
1923
|
+
* a ``javascript:`` link target is rendered as a live ``href``;
|
|
1924
|
+
* ``<javascript:alert(1)>`` is a CommonMark autolink, which accepts any
|
|
1925
|
+
scheme, so it too goes out as a live ``href``.
|
|
1926
|
+
|
|
1927
|
+
Reading does not filter either: a body's ``javascript:`` link reads back as
|
|
1928
|
+
a ``javascript:`` link. What reading does guarantee is that text a body
|
|
1929
|
+
*displays* stays text: ``<script>`` reads back as ``\<script>``, and is
|
|
1930
|
+
written back as the same ``<script>``.
|
|
1931
|
+
|
|
1932
|
+
A caller relaying Markdown it did not write must neutralise raw HTML and
|
|
1933
|
+
executable link schemes before writing it. A sync between two ITSMs is one
|
|
1934
|
+
such caller.
|
|
1935
|
+
|
|
1936
|
+
What survives a round trip
|
|
1937
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
1938
|
+
|
|
1939
|
+
**Markdown written and read back.** Paragraphs, ``*emphasis*`` and
|
|
1940
|
+
``**strong**``, ATX headings, nested lists, numbered lists, block quotes,
|
|
1941
|
+
fences with their language, links with titles, autolinks, raw ``<u>`` and
|
|
1942
|
+
``<s>`` spans, accents and escaped literal text all come back as they were
|
|
1943
|
+
written. Other spellings come back in mdformat's canonical form, and display
|
|
1944
|
+
the same:
|
|
1945
|
+
|
|
1946
|
+
* ``_em_`` and ``__strong__`` as ``*em*`` and ``**strong**``;
|
|
1947
|
+
* ``+`` and ``*`` bullets as ``-``, ``1)`` as ``1.``, and repeated ``1.``
|
|
1948
|
+
items numbered ``1.``, ``2.``, ``3.``;
|
|
1949
|
+
* a setext heading as an ATX one, and ``***`` as ``---``;
|
|
1950
|
+
* a table's delimiter row as ``| -- |``;
|
|
1951
|
+
* a lone newline, or two trailing spaces, as a backslash hard break;
|
|
1952
|
+
* a link target holding parentheses wrapped in ``<...>``;
|
|
1953
|
+
* runs of blank lines as one, and a character reference such as ``©``
|
|
1954
|
+
as the character.
|
|
1955
|
+
|
|
1956
|
+
A few things do not come back:
|
|
1957
|
+
|
|
1958
|
+
* text in angle brackets that is not a URL, ``use the <Enter> key``, is sent
|
|
1959
|
+
as a live unknown tag, and the browser and the reader both drop it;
|
|
1960
|
+
* an e-mail autolink comes back as an inline ``mailto:`` link;
|
|
1961
|
+
* a ``[`` or ``]`` in a link target comes back percent-encoded, as ``%5B``
|
|
1962
|
+
and ``%5D``;
|
|
1963
|
+
* a no-break space or hard break at the end of a paragraph is dropped;
|
|
1964
|
+
* a line holding only ``*`` is an empty list item in CommonMark, and reads
|
|
1965
|
+
back as nothing.
|
|
1966
|
+
|
|
1967
|
+
After that first cycle, a further one changes nothing more, with two
|
|
1968
|
+
exceptions found with generated Markdown (2026-10-02, 23 of 4,500 bodies):
|
|
1969
|
+
two adjacent lists with different bullets read as one loose list, then as
|
|
1970
|
+
one tight list; and a fence whose info string holds a character reference,
|
|
1971
|
+
such as ``&amp;``, loses one level of it on each cycle.
|
|
1972
|
+
|
|
1973
|
+
**A body read, written back and read again.** This direction is held to
|
|
1974
|
+
more. The aim is that what the Markdown displays is what GLPI displayed, and
|
|
1975
|
+
that the Markdown is a fixed point from the first read. The converter's
|
|
1976
|
+
tests check both, by comparing what the HTML and the rendered Markdown
|
|
1977
|
+
display, on realistic and generated bodies. It is an aim, not a guarantee
|
|
1978
|
+
for every body.
|
|
1979
|
+
|
|
1980
|
+
A few structures have no Markdown spelling. They keep their words and lose
|
|
1981
|
+
only their shape:
|
|
1982
|
+
|
|
1983
|
+
* **a table nested in a table** has its grid flattened: each inner cell
|
|
1984
|
+
becomes text in the outer cell, spaced apart, with every word kept and in
|
|
1985
|
+
order. E-mail signatures and notification templates are often laid out
|
|
1986
|
+
so. A table inside a heading or a link is flattened the same way;
|
|
1987
|
+
* **a table without a header row** gains an empty one, because GFM requires
|
|
1988
|
+
a header;
|
|
1989
|
+
* a table cell holds one line, so the paragraphs inside a cell join, while a
|
|
1990
|
+
``<br>`` in a cell is kept as a raw ``<br>``;
|
|
1991
|
+
* a heading holds one line, so a ``<br>`` in a heading becomes a space;
|
|
1992
|
+
* trailing spaces at the end of a ``<pre>`` are dropped;
|
|
1993
|
+
* underline, highlight, insertion and strike are kept only as raw ``<u>``,
|
|
1994
|
+
``<mark>``, ``<ins>`` and ``<s>``, so a renderer that drops raw HTML shows
|
|
1995
|
+
their text without the formatting;
|
|
1996
|
+
* two code spans with nothing between them become one span, which shows the
|
|
1997
|
+
two backticks that joined them.
|
|
1998
|
+
|
|
1999
|
+
**Known holes.** These shapes lose words, or display other than GLPI did.
|
|
2000
|
+
Each was found with synthetic input and reproduced on 2026-10-02:
|
|
2001
|
+
|
|
2002
|
+
* a table row with more cells than the table's first row loses the extra
|
|
2003
|
+
cells, with their words, at the first read;
|
|
2004
|
+
* a ``|`` inside a ``<pre>``, a link target or a title in a table cell
|
|
2005
|
+
splits the row: the rest of the cell is lost or shows as Markdown source;
|
|
2006
|
+
* a ``<caption>`` or ``<colgroup>`` directly inside a ``<table>``, with no
|
|
2007
|
+
``<tbody>``, turns the table into lines of literal pipes;
|
|
2008
|
+
* an image whose title holds a ``"`` is lost, and shows as its Markdown
|
|
2009
|
+
source;
|
|
2010
|
+
* a list or an ``<hr>`` inside a table cell shows as ``-`` or ``---`` text;
|
|
2011
|
+
* a heading followed by text in the same cell, or text sitting directly in
|
|
2012
|
+
a nested table after its rows, runs into the next word (``titresuite``);
|
|
2013
|
+
* a table whose ``<td>`` and ``<tr>`` are never closed folds into one cell,
|
|
2014
|
+
keeping its words;
|
|
2015
|
+
* a definition list (``<dl>``) reads as a ``term`` line and a
|
|
2016
|
+
``: definition`` line, so its display gains the colon;
|
|
2017
|
+
* bold, italic or struck text (``<b>``, ``<em>``, ``<s>`` and their
|
|
2018
|
+
synonyms) wrapped round blocks, other than a single paragraph, shows its
|
|
2019
|
+
markers or its tag as text, and a list, a table or a heading inside it as
|
|
2020
|
+
Markdown source; round a ``<pre>`` it leaves a fence open, so the rest of
|
|
2021
|
+
the body shows as code. 0.6.0 read these the same way.
|
|
2022
|
+
|
|
2023
|
+
Some shapes are not fixed points at the first read, and settle after one
|
|
2024
|
+
more cycle. Adjacent lists read as one loose list, then as one tight list. A
|
|
2025
|
+
definition list's newline becomes a hard break. Bold inside bold
|
|
2026
|
+
(``<b><b>gras</b></b>``) reads as ``****gras****``, then as ``**gras**``. A
|
|
2027
|
+
link or image title loses a backslash before punctuation. The caption,
|
|
2028
|
+
colgroup and image-title shapes above settle too, on what they already lost.
|
|
2029
|
+
|
|
1818
2030
|
Retry behaviour
|
|
1819
2031
|
~~~~~~~~~~~~~~~
|
|
1820
2032
|
|
|
@@ -149,8 +149,8 @@ class GlpiContentError(GlpiError):
|
|
|
149
149
|
|
|
150
150
|
This exists so that no failure of the content layer escapes the
|
|
151
151
|
package's taxonomy. The conversion runs third-party parsers
|
|
152
|
-
(``markdownify`` inbound, ``
|
|
153
|
-
used to reach the caller as a bare builtin — most visibly a
|
|
152
|
+
(``markdownify`` and ``mdformat`` inbound, ``cmark-gfm`` outbound), and a
|
|
153
|
+
parser fault used to reach the caller as a bare builtin — most visibly a
|
|
154
154
|
``RecursionError``, which ``except GlpiError`` does not catch and which
|
|
155
155
|
a caller reading a ticket has no reason to expect from
|
|
156
156
|
``get_ticket``. Deeply nested HTML is caught and answered with the
|