glpi-python-client 0.6.1__tar.gz → 0.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/CHANGELOG.md +43 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/PKG-INFO +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/user_guide.rst +60 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/__init__.py +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/_testing.py +4 -4
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/_testing.py +4 -4
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/content/__init__.py +6 -2
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/content/conversion.py +293 -22
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/pyproject.toml +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-asset-workflow/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-client-setup/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-contract-workflow/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-document-workflow/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-knowledge-base/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-plugin-fields/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-reporting-and-context/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-team-members/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-ticket-timeline/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-ticket-workflow/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-user-location-provisioning/SKILL.md +1 -1
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/.gitignore +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/.pre-commit-config.yaml +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/.readthedocs.yaml +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/CONTRIBUTING.md +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/LICENSE +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/README.md +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/_static/.gitkeep +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/api_reference.rst +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/conf.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/development.md +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/development_rtd.rst +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/index.rst +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/installation.rst +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/publishing.md +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/publishing_rtd.rst +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/docs/sponsoring.rst +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/_concurrency.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/auth/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/auth/_v1_session.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/auth/auth.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/_base_client.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/administration/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/administration/_entity.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/administration/_user.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assets/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assets/_computer.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assistance/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assistance/_team.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assistance/_ticket.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/dropdowns/_contract_type.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/dropdowns/_location.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/knowledgebase/_article.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/knowledgebase/_revision.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/management/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/management/_contract.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/management/_document.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/plugins/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/api/plugins/_fields.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/client.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/commons/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/commons/_config.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/commons/_constants.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/commons/_filters.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/commons/_http.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/commons/_payloads.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/commons/_transport.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/custom/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/custom/_statistics.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_async/clients/custom/_ticket_context.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_errors.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/_concurrency.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/auth/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/auth/_v1_session.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/auth/auth.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/_base_client.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/administration/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/administration/_entity.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/administration/_user.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assets/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assets/_computer.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assistance/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assistance/_team.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assistance/_ticket.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/dropdowns/_contract_type.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/dropdowns/_location.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/knowledgebase/_article.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/knowledgebase/_revision.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/management/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/management/_contract.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/management/_document.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/plugins/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/api/plugins/_fields.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/client.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/commons/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/commons/_config.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/commons/_constants.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/commons/_filters.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/commons/_http.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/commons/_payloads.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/commons/_transport.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/custom/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/custom/_statistics.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/_sync/clients/custom/_ticket_context.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/_base.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/_common.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/_content.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/administration/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/administration/_entity.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/administration/_user.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assets/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assets/_computer.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assets/_contract_item.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assistance/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assistance/_team.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assistance/_ticket.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assistance/timeline/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assistance/timeline/_document.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assistance/timeline/_followup.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assistance/timeline/_solution.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/assistance/timeline/_task.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/dropdowns/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/dropdowns/_contract_type.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/dropdowns/_location.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/enums.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/knowledgebase/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/knowledgebase/_article.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/knowledgebase/_category.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/knowledgebase/_comment.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/knowledgebase/_revision.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/management/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/management/_contract.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/management/_contract_cost.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/management/_document.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/plugins/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/api_schema/plugins/_fields.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/custom_schema/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/models/custom_schema/_ticket_context.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/py.typed +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/rsql.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/testing/__init__.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/testing/fixtures.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/testing/utils.py +0 -0
- {glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/README.md +0 -0
|
@@ -4,6 +4,49 @@ 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.3 — 2026-10-08
|
|
8
|
+
|
|
9
|
+
A patch release of the converter's reading: a caller can recognise the
|
|
10
|
+
documents a body embeds or links and decide, link by link, what a read
|
|
11
|
+
writes. Without the new argument a read is unchanged, byte for byte; writing
|
|
12
|
+
is unchanged. `easyvista-python-client` 0.4.3 adds the same hook.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`from_transport(..., rewrite_link=callback)`.** The callback is called with
|
|
17
|
+
a `Link` for each link and image the read meets outside code -- an image
|
|
18
|
+
before the link around it, with that link's `enclosing_href` -- and its
|
|
19
|
+
answer is written: `None` keeps the reader's output, a `str` is written as
|
|
20
|
+
literal text, a `Link` replaces the target (an empty `href` drops a link and
|
|
21
|
+
keeps its content, or writes an image as its text). The callback is held in
|
|
22
|
+
a context variable, so concurrent reads each use their own. What it raises
|
|
23
|
+
reaches the caller unchanged; a `ValueError` is not taken for markdownify's
|
|
24
|
+
and answered with the text fallback.
|
|
25
|
+
- **`Link` and `RewriteLink`**, exported from `glpi_python_client.content`.
|
|
26
|
+
- **`GlpiContentConverter.document_id_of(url)`** returns the document id a URL
|
|
27
|
+
to `front/document.send.php` names (relative, rooted or absolute, one
|
|
28
|
+
`docid` in any position), and **`document_image(document_id, alt=...,
|
|
29
|
+
itemtype=..., items_id=...)`** the Markdown of an embedded document image as
|
|
30
|
+
GLPI's editor writes one -- the image inside a link to the same URL --
|
|
31
|
+
spelled as the reader spells it. A `Link` whose URL names a document carries
|
|
32
|
+
its `document_id`.
|
|
33
|
+
|
|
34
|
+
## 0.6.2 — 2026-10-07
|
|
35
|
+
|
|
36
|
+
A patch release of the converter's writing: a link written into GLPI opens in
|
|
37
|
+
a new window. Reading is unchanged. `easyvista-python-client` 0.4.2 makes the
|
|
38
|
+
same change, so the two converters still write the same HTML.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **Every link `to_transport` writes carries `target="_blank"
|
|
43
|
+
rel="noopener noreferrer"`**, as a link written in GLPI's own editor carries
|
|
44
|
+
`target="_blank"`. Without it a link opens in place of the page showing it.
|
|
45
|
+
Write models render through the same function, so a followup, task,
|
|
46
|
+
solution or ticket body written through them carries it too. Reading
|
|
47
|
+
ignores both attributes, so `from_transport(to_transport(m))` is unchanged
|
|
48
|
+
for every link.
|
|
49
|
+
|
|
7
50
|
## 0.6.1 — 2026-10-02
|
|
8
51
|
|
|
9
52
|
The reader now gives the same Markdown as `easyvista-python-client` 0.4.1,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: glpi-python-client
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: A typed Python client for GLPI ITSM APIs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/baraline/glpi_python_client
|
|
6
6
|
Project-URL: Documentation, https://glpi-python-client.readthedocs.io/en/latest/
|
|
@@ -1838,7 +1838,9 @@ becomes:
|
|
|
1838
1838
|
|
|
1839
1839
|
Writing, your Markdown is rendered by cmark-gfm. A newline is a line break,
|
|
1840
1840
|
GFM tables work, and raw HTML passes through, so put a placeholder such as
|
|
1841
|
-
``<login>`` in backticks. A
|
|
1841
|
+
``<login>`` in backticks. A link opens in a new window, as a link written in
|
|
1842
|
+
GLPI's editor does: each ``<a href>`` the renderer writes carries
|
|
1843
|
+
``target="_blank" rel="noopener noreferrer"``, which reading ignores. A write model keeps your Markdown as written,
|
|
1842
1844
|
stripped at both ends, unless it starts with ``<`` and holds an HTML
|
|
1843
1845
|
element anywhere, in which case it is read as HTML. Stripping unindents the
|
|
1844
1846
|
first line of a body that opens with an indented code block, which then
|
|
@@ -1910,6 +1912,63 @@ text.
|
|
|
1910
1912
|
Prefer a patched interpreter anyway: the guard covers the converter's input,
|
|
1911
1913
|
and the CPython fix covers the parser itself.
|
|
1912
1914
|
|
|
1915
|
+
Document images, and rewriting links as they are read
|
|
1916
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
1917
|
+
|
|
1918
|
+
GLPI's editor keeps an image pasted into a body as a document and embeds it
|
|
1919
|
+
from the page that serves documents, inside a link to the same URL:
|
|
1920
|
+
``<a href="/front/document.send.php?docid=N&itemtype=Ticket&items_id=T">``
|
|
1921
|
+
around ``<img src="...same URL...">``. Without a callback the reader writes
|
|
1922
|
+
that as any linked image, ``[](url)``, which displays nowhere but in
|
|
1923
|
+
GLPI. Three helpers let a caller do better.
|
|
1924
|
+
|
|
1925
|
+
``GlpiContentConverter.document_id_of(url)`` returns the document id a URL
|
|
1926
|
+
names -- ``front/document.send.php`` with one ``docid``, relative, rooted or
|
|
1927
|
+
absolute, under any path -- or ``None``. ``document_image(document_id,
|
|
1928
|
+
alt=..., itemtype=..., items_id=...)`` returns the Markdown of the editor's
|
|
1929
|
+
form, spelled exactly as the reader spells it, so ``to_transport`` writes it
|
|
1930
|
+
and ``from_transport`` reads it back unchanged. Its URL is rooted, as the
|
|
1931
|
+
editor wrote it on the instance measured.
|
|
1932
|
+
|
|
1933
|
+
``from_transport(..., rewrite_link=callback)`` calls ``callback`` with a
|
|
1934
|
+
:class:`~glpi_python_client.content.Link` for each link and each image it
|
|
1935
|
+
meets outside code, an image before the link around it. A ``Link`` carries the
|
|
1936
|
+
``href`` (an image's ``src``), the ``text`` (an image's ``alt``), the
|
|
1937
|
+
``title``, whether it is an ``image``, the ``document_id`` its URL names, and
|
|
1938
|
+
for an image inside a link that link's ``enclosing_href``. What the callback
|
|
1939
|
+
answers is written:
|
|
1940
|
+
|
|
1941
|
+
* ``None``: what the reader writes without a callback;
|
|
1942
|
+
* a ``str``: that text, literally -- escaped as any text the body displays, so
|
|
1943
|
+
it cannot become markup;
|
|
1944
|
+
* a ``Link``: a link's ``href`` and ``title`` around the link's content as
|
|
1945
|
+
read, or an image's ``href`` as its ``src``, ``text`` as its ``alt`` and
|
|
1946
|
+
``title``;
|
|
1947
|
+
* a ``Link`` with an empty ``href``: a link is dropped and its content kept, an
|
|
1948
|
+
image is written as its ``text``.
|
|
1949
|
+
|
|
1950
|
+
.. code-block:: python
|
|
1951
|
+
|
|
1952
|
+
from glpi_python_client.content import GlpiContentConverter, Link
|
|
1953
|
+
|
|
1954
|
+
def name_documents(link: Link) -> Link | str | None:
|
|
1955
|
+
if link.document_id is None:
|
|
1956
|
+
return None
|
|
1957
|
+
if link.image:
|
|
1958
|
+
return f"[document {link.document_id}]"
|
|
1959
|
+
return Link("") # the editor's link around it: keep only its content
|
|
1960
|
+
|
|
1961
|
+
GlpiContentConverter.from_transport(body, rewrite_link=name_documents)
|
|
1962
|
+
|
|
1963
|
+
The callback is held for the duration of one read, per thread and per task,
|
|
1964
|
+
so concurrent reads each use their own, and a read inside a callback has its
|
|
1965
|
+
own too. What the callback raises reaches the caller unchanged -- a
|
|
1966
|
+
``ValueError`` included, which the reader would otherwise take for one of
|
|
1967
|
+
markdownify's and answer by reading the body as its text. An answer of any
|
|
1968
|
+
other type raises ``TypeError``. It is not called for a value passed through
|
|
1969
|
+
as Markdown (``plain_text_is_markdown=True``), nor for a body read as its text.
|
|
1970
|
+
``easyvista-python-client`` 0.4.3 carries the same hook.
|
|
1971
|
+
|
|
1913
1972
|
It is not a sanitiser
|
|
1914
1973
|
^^^^^^^^^^^^^^^^^^^^^
|
|
1915
1974
|
|
|
@@ -127,10 +127,10 @@ class TransportRecorder:
|
|
|
127
127
|
)
|
|
128
128
|
return FakeResponse(status_code=self._delete_status, payload={})
|
|
129
129
|
|
|
130
|
-
client._get_request = _get # type: ignore[method-assign, assignment]
|
|
131
|
-
client._post_request = _post # type: ignore[method-assign, assignment]
|
|
132
|
-
client._update_request = _patch # type: ignore[method-assign, assignment]
|
|
133
|
-
client._delete_request = _delete # type: ignore[method-assign, assignment]
|
|
130
|
+
client._get_request = _get # type: ignore[method-assign, assignment, unused-ignore]
|
|
131
|
+
client._post_request = _post # type: ignore[method-assign, assignment, unused-ignore]
|
|
132
|
+
client._update_request = _patch # type: ignore[method-assign, assignment, unused-ignore]
|
|
133
|
+
client._delete_request = _delete # type: ignore[method-assign, assignment, unused-ignore]
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
class FailingTransportRecorder(TransportRecorder):
|
|
@@ -127,10 +127,10 @@ class TransportRecorder:
|
|
|
127
127
|
)
|
|
128
128
|
return FakeResponse(status_code=self._delete_status, payload={})
|
|
129
129
|
|
|
130
|
-
client._get_request = _get # type: ignore[method-assign, assignment]
|
|
131
|
-
client._post_request = _post # type: ignore[method-assign, assignment]
|
|
132
|
-
client._update_request = _patch # type: ignore[method-assign, assignment]
|
|
133
|
-
client._delete_request = _delete # type: ignore[method-assign, assignment]
|
|
130
|
+
client._get_request = _get # type: ignore[method-assign, assignment, unused-ignore]
|
|
131
|
+
client._post_request = _post # type: ignore[method-assign, assignment, unused-ignore]
|
|
132
|
+
client._update_request = _patch # type: ignore[method-assign, assignment, unused-ignore]
|
|
133
|
+
client._delete_request = _delete # type: ignore[method-assign, assignment, unused-ignore]
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
class FailingTransportRecorder(TransportRecorder):
|
{glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/content/__init__.py
RENAMED
|
@@ -6,6 +6,10 @@ payloads and the package's canonical Markdown and typed-record representations.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from glpi_python_client.content.conversion import
|
|
9
|
+
from glpi_python_client.content.conversion import (
|
|
10
|
+
GlpiContentConverter,
|
|
11
|
+
Link,
|
|
12
|
+
RewriteLink,
|
|
13
|
+
)
|
|
10
14
|
|
|
11
|
-
__all__ = ["GlpiContentConverter"]
|
|
15
|
+
__all__ = ["GlpiContentConverter", "Link", "RewriteLink"]
|
{glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/glpi_python_client/content/conversion.py
RENAMED
|
@@ -40,9 +40,12 @@ import re
|
|
|
40
40
|
import string
|
|
41
41
|
import unicodedata
|
|
42
42
|
from collections.abc import Callable, Iterator, Mapping, MutableMapping, Sequence
|
|
43
|
+
from contextvars import ContextVar
|
|
44
|
+
from dataclasses import dataclass
|
|
43
45
|
from functools import cached_property
|
|
44
46
|
from html import escape, unescape
|
|
45
47
|
from typing import Any, cast
|
|
48
|
+
from urllib.parse import parse_qs, urlsplit
|
|
46
49
|
|
|
47
50
|
import cmarkgfm
|
|
48
51
|
import mdformat_tables
|
|
@@ -59,7 +62,7 @@ from mdformat.renderer import (
|
|
|
59
62
|
)
|
|
60
63
|
from mdformat.renderer.typing import Postprocess
|
|
61
64
|
|
|
62
|
-
from glpi_python_client._errors import GlpiContentError
|
|
65
|
+
from glpi_python_client._errors import GlpiContentError, GlpiValidationError
|
|
63
66
|
|
|
64
67
|
#: Element names that make a ``<...>`` sequence markup rather than text: the
|
|
65
68
|
#: HTML5 elements, then the obsolete ones old editors and mail clients write.
|
|
@@ -363,6 +366,97 @@ def _title(title: str) -> str:
|
|
|
363
366
|
return ' "' + re.sub(r'[\\"]', r"\\\g<0>", title) + '"' if title else ""
|
|
364
367
|
|
|
365
368
|
|
|
369
|
+
#: The page GLPI serves a document from. Its editor embeds a pasted image as
|
|
370
|
+
#: ``<img src=".../front/document.send.php?docid=N&itemtype=Ticket&items_id=T">``
|
|
371
|
+
#: inside a link to the same URL, and links an attachment the same way.
|
|
372
|
+
_DOCUMENT_PAGE = "front/document.send.php"
|
|
373
|
+
|
|
374
|
+
#: A document id, as the ``docid`` parameter carries one: ASCII digits only,
|
|
375
|
+
#: since ``int`` also reads other scripts' digits.
|
|
376
|
+
_DOCUMENT_ID = re.compile(r"[0-9]{1,18}")
|
|
377
|
+
|
|
378
|
+
#: An item type, as ``itemtype`` names one: a PHP class name.
|
|
379
|
+
_ITEMTYPE = re.compile(r"[A-Za-z_][A-Za-z0-9_\\]{0,99}")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@dataclass(frozen=True, slots=True)
|
|
383
|
+
class Link:
|
|
384
|
+
"""A link or an image the reader meets, as a ``rewrite_link`` callback sees it.
|
|
385
|
+
|
|
386
|
+
Attributes
|
|
387
|
+
----------
|
|
388
|
+
href : str
|
|
389
|
+
An ``<a>``'s ``href`` or an ``<img>``'s ``src``, character references
|
|
390
|
+
decoded.
|
|
391
|
+
text : str
|
|
392
|
+
An ``<a>``'s displayed text or an ``<img>``'s ``alt``, whitespace
|
|
393
|
+
collapsed.
|
|
394
|
+
title : str
|
|
395
|
+
The ``title``, or ``""``.
|
|
396
|
+
image : bool
|
|
397
|
+
``True`` for an ``<img>``.
|
|
398
|
+
document_id : int or None
|
|
399
|
+
For a link or an image whose URL is GLPI's document page with a
|
|
400
|
+
``docid`` (:meth:`GlpiContentConverter.document_id_of`), that id;
|
|
401
|
+
``None`` otherwise.
|
|
402
|
+
enclosing_href : str or None
|
|
403
|
+
For an ``<img>`` inside a link, that link's ``href``; ``None``
|
|
404
|
+
otherwise. The image is read before the link around it.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
href: str
|
|
408
|
+
text: str = ""
|
|
409
|
+
title: str = ""
|
|
410
|
+
image: bool = False
|
|
411
|
+
document_id: int | None = None
|
|
412
|
+
enclosing_href: str | None = None
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
#: A ``rewrite_link`` callback: called with each link and image a read meets,
|
|
416
|
+
#: outside code. ``None`` keeps what the reader writes without one. A ``str`` is
|
|
417
|
+
#: written instead, as literal text. A :class:`Link` is written in the reader's
|
|
418
|
+
#: own spelling: a link's ``href`` and ``title``, around the link's content as
|
|
419
|
+
#: read; an image's ``href`` as its ``src``, ``text`` as its ``alt`` and
|
|
420
|
+
#: ``title``. An empty ``href`` drops a link and keeps its content, and writes an
|
|
421
|
+
#: image as its ``text``.
|
|
422
|
+
RewriteLink = Callable[[Link], "Link | str | None"]
|
|
423
|
+
|
|
424
|
+
_REWRITE: ContextVar[RewriteLink | None] = ContextVar("rewrite_link", default=None)
|
|
425
|
+
"""The callback of the read in progress. A context variable rather than state
|
|
426
|
+
on the shared converter: each thread and each task reads with its own."""
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class _CallbackRaised(Exception):
|
|
430
|
+
"""What a ``rewrite_link`` callback raised, carried past the reader's fallbacks.
|
|
431
|
+
|
|
432
|
+
The reader answers a ``ValueError`` by reading the body as its text; a
|
|
433
|
+
callback's own ``ValueError`` must not pass for markdownify's and quietly
|
|
434
|
+
cost the body its formatting.
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
def __init__(self, error: Exception) -> None:
|
|
438
|
+
super().__init__(error)
|
|
439
|
+
self.error = error
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _rewritten(link: Link) -> Link | str | None:
|
|
443
|
+
"""Ask the read's callback about ``link``; ``None`` when there is none."""
|
|
444
|
+
|
|
445
|
+
rewrite = _REWRITE.get()
|
|
446
|
+
if rewrite is None:
|
|
447
|
+
return None
|
|
448
|
+
try:
|
|
449
|
+
answer: object = rewrite(link) # typed as what it is, not what it should be
|
|
450
|
+
except Exception as exc:
|
|
451
|
+
raise _CallbackRaised(exc) from exc
|
|
452
|
+
if answer is None or isinstance(answer, (Link, str)):
|
|
453
|
+
return answer
|
|
454
|
+
error = TypeError(
|
|
455
|
+
f"rewrite_link returned {type(answer).__name__}; expected Link, str or None"
|
|
456
|
+
)
|
|
457
|
+
raise _CallbackRaised(error)
|
|
458
|
+
|
|
459
|
+
|
|
366
460
|
class _Converter(MarkdownConverter):
|
|
367
461
|
"""``markdownify``, with what CommonMark and the reader's glue need on top.
|
|
368
462
|
|
|
@@ -520,15 +614,34 @@ class _Converter(MarkdownConverter):
|
|
|
520
614
|
body, _, tail = rest.rpartition("```")
|
|
521
615
|
return f"{head}{fence}{body}{fence}{tail}"
|
|
522
616
|
|
|
617
|
+
def _literal(self, text: str, parent_tags: set[str]) -> str:
|
|
618
|
+
"""``text`` escaped as the reader escapes a text node: displayed as it is."""
|
|
619
|
+
|
|
620
|
+
return self.escape(" ".join(text.split()), parent_tags)
|
|
621
|
+
|
|
523
622
|
def convert_a(self, el: Tag, text: str, parent_tags: set[str]) -> str:
|
|
524
623
|
"""A link; ``<url>`` when its text is its URL and reads back unchanged."""
|
|
525
624
|
|
|
625
|
+
if "_noformat" in parent_tags:
|
|
626
|
+
return text
|
|
526
627
|
href = str(el.get("href") or "")
|
|
628
|
+
title = str(el.get("title") or "")
|
|
629
|
+
answer = _rewritten(
|
|
630
|
+
Link(
|
|
631
|
+
href,
|
|
632
|
+
" ".join(el.get_text().split()),
|
|
633
|
+
title,
|
|
634
|
+
document_id=GlpiContentConverter.document_id_of(href),
|
|
635
|
+
)
|
|
636
|
+
)
|
|
637
|
+
if isinstance(answer, str):
|
|
638
|
+
return self._literal(answer, parent_tags)
|
|
639
|
+
if answer is not None:
|
|
640
|
+
href, title = answer.href, answer.title
|
|
527
641
|
edges = _EDGES.fullmatch(text)
|
|
528
|
-
if
|
|
642
|
+
if not href or edges is None or not edges[2]:
|
|
529
643
|
return text
|
|
530
644
|
before, inner, after = edges.groups()
|
|
531
|
-
title = str(el.get("title") or "")
|
|
532
645
|
if not title and el.get_text() == href and _AUTOLINK.fullmatch(href):
|
|
533
646
|
return f"{before}<{href}>{after}"
|
|
534
647
|
return f"{before}[{inner}]({_destination(href)}{_title(title)}){after}"
|
|
@@ -536,12 +649,37 @@ class _Converter(MarkdownConverter):
|
|
|
536
649
|
def convert_img(self, el: Tag, text: str, parent_tags: set[str]) -> str:
|
|
537
650
|
"""An image, wherever it is: a cell or a heading holds one too."""
|
|
538
651
|
|
|
652
|
+
src = str(el.get("src") or "")
|
|
539
653
|
alt = " ".join(str(el.get("alt") or "").split())
|
|
654
|
+
title = str(el.get("title") or "")
|
|
655
|
+
if "_noformat" not in parent_tags:
|
|
656
|
+
around = el.find_parent("a")
|
|
657
|
+
answer = _rewritten(
|
|
658
|
+
Link(
|
|
659
|
+
src,
|
|
660
|
+
alt,
|
|
661
|
+
title,
|
|
662
|
+
image=True,
|
|
663
|
+
document_id=GlpiContentConverter.document_id_of(src),
|
|
664
|
+
enclosing_href=(
|
|
665
|
+
None if around is None else str(around.get("href") or "")
|
|
666
|
+
),
|
|
667
|
+
)
|
|
668
|
+
)
|
|
669
|
+
if isinstance(answer, str):
|
|
670
|
+
return self._literal(answer, parent_tags)
|
|
671
|
+
if answer is not None and not answer.href:
|
|
672
|
+
return self._literal(answer.text, parent_tags)
|
|
673
|
+
if answer is not None:
|
|
674
|
+
src, alt, title = (
|
|
675
|
+
answer.href,
|
|
676
|
+
" ".join(answer.text.split()),
|
|
677
|
+
answer.title,
|
|
678
|
+
)
|
|
540
679
|
alt = str(self._inherited("escape")(alt, parent_tags))
|
|
541
680
|
if alt.startswith("^") and "_noformat" not in parent_tags:
|
|
542
681
|
alt = "\\" + alt # cmark-gfm reads " or ''))})"
|
|
682
|
+
return f"}{_title(title)})"
|
|
545
683
|
|
|
546
684
|
|
|
547
685
|
class _Node(RenderTreeNode):
|
|
@@ -715,12 +853,27 @@ def html_to_markdown(html: str) -> str:
|
|
|
715
853
|
return str(_FORMATTER.render(_CONVERTER.convert_soup(soup))).strip()
|
|
716
854
|
|
|
717
855
|
|
|
856
|
+
#: A link as cmark-gfm writes one: an ``href``, an optional ``title``, nothing else.
|
|
857
|
+
_RENDERED_LINK = re.compile(r'<a href="[^"]*"(?: title="[^"]*")?>')
|
|
858
|
+
|
|
859
|
+
#: What GLPI's own editor writes on a link, so that it opens in a new window
|
|
860
|
+
#: rather than in place of the page showing it. ``noreferrer`` too, as
|
|
861
|
+
#: ``easyvista-python-client``'s twin writes, so the two render alike.
|
|
862
|
+
_NEW_WINDOW = ' target="_blank" rel="noopener noreferrer"'
|
|
863
|
+
|
|
864
|
+
|
|
718
865
|
def markdown_to_html(markdown: str) -> str:
|
|
719
|
-
"""Render Markdown as HTML: CommonMark with GFM tables, through cmark-gfm.
|
|
866
|
+
"""Render Markdown as HTML: CommonMark with GFM tables, through cmark-gfm.
|
|
867
|
+
|
|
868
|
+
Every link opens in a new window, the way a link written in GLPI's editor
|
|
869
|
+
does: ``target="_blank" rel="noopener noreferrer"`` is added to each link
|
|
870
|
+
cmark-gfm writes. Reading ignores both attributes.
|
|
871
|
+
"""
|
|
720
872
|
|
|
721
873
|
html: str = cmarkgfm.markdown_to_html_with_extensions(
|
|
722
874
|
markdown, options=_RENDER_OPTIONS, extensions=["table"]
|
|
723
875
|
)
|
|
876
|
+
html = _RENDERED_LINK.sub(lambda link: link.group(0)[:-1] + _NEW_WINDOW + ">", html)
|
|
724
877
|
return html.strip()
|
|
725
878
|
|
|
726
879
|
|
|
@@ -737,11 +890,45 @@ def _text_of(html: str) -> str:
|
|
|
737
890
|
return "\n".join(line for line in lines if line)
|
|
738
891
|
|
|
739
892
|
|
|
893
|
+
def _converted(content: str) -> str:
|
|
894
|
+
"""Read HTML, falling back to its text where the conversion cannot."""
|
|
895
|
+
|
|
896
|
+
try:
|
|
897
|
+
try:
|
|
898
|
+
return html_to_markdown(content)
|
|
899
|
+
except (RecursionError, ValueError): # ValueError: markdownify's
|
|
900
|
+
# int() of a colspan or start such as "²" or 5,000 digits
|
|
901
|
+
return html_to_markdown(_plain_text_html(_text_of(content)))
|
|
902
|
+
except ParserRejectedMarkup:
|
|
903
|
+
# html.parser gives up on a few malformed declarations; the
|
|
904
|
+
# body's words are still worth more than an exception.
|
|
905
|
+
text = unescape(_ANY_TAG.sub(" ", content))
|
|
906
|
+
return html_to_markdown(_plain_text_html(" ".join(text.split())))
|
|
907
|
+
except _CallbackRaised:
|
|
908
|
+
raise
|
|
909
|
+
except Exception as exc:
|
|
910
|
+
raise GlpiContentError(
|
|
911
|
+
"Could not convert GLPI HTML content to Markdown "
|
|
912
|
+
f"({type(exc).__name__}: {exc})."
|
|
913
|
+
) from exc
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def _positive(value: object) -> bool:
|
|
917
|
+
"""Whether ``value`` is a positive ``int`` and not a ``bool``."""
|
|
918
|
+
|
|
919
|
+
return isinstance(value, int) and not isinstance(value, bool) and value > 0
|
|
920
|
+
|
|
921
|
+
|
|
740
922
|
class GlpiContentConverter:
|
|
741
923
|
"""Convert content between GLPI HTML payloads and canonical Markdown."""
|
|
742
924
|
|
|
743
925
|
@staticmethod
|
|
744
|
-
def from_transport(
|
|
926
|
+
def from_transport(
|
|
927
|
+
value: object,
|
|
928
|
+
*,
|
|
929
|
+
plain_text_is_markdown: bool = False,
|
|
930
|
+
rewrite_link: RewriteLink | None = None,
|
|
931
|
+
) -> str:
|
|
745
932
|
"""Convert one GLPI transport value into Markdown.
|
|
746
933
|
|
|
747
934
|
Parameters
|
|
@@ -759,6 +946,12 @@ class GlpiContentConverter:
|
|
|
759
946
|
Markdown, but Markdown that opens with an autolink or other
|
|
760
947
|
angle-bracketed text and carries inline HTML further on is read
|
|
761
948
|
as HTML, and loses that autolink.
|
|
949
|
+
rewrite_link : callable, optional
|
|
950
|
+
Called with a :class:`Link` for each link and image the read
|
|
951
|
+
meets outside code, an image before the link around it; its
|
|
952
|
+
answer decides what is written (:data:`RewriteLink`). Not called
|
|
953
|
+
for a value passed through as Markdown, nor for a body read as
|
|
954
|
+
its text. Without one, the read is what it always was.
|
|
762
955
|
|
|
763
956
|
Returns
|
|
764
957
|
-------
|
|
@@ -778,6 +971,10 @@ class GlpiContentConverter:
|
|
|
778
971
|
limit, where no stack is left even to report the failure as
|
|
779
972
|
``GlpiContentError`` (the user guide gives the measured
|
|
780
973
|
depths).
|
|
974
|
+
Exception
|
|
975
|
+
Whatever ``rewrite_link`` raised, unchanged; ``TypeError`` when it
|
|
976
|
+
answered something other than a :class:`Link`, a ``str`` or
|
|
977
|
+
``None``.
|
|
781
978
|
"""
|
|
782
979
|
|
|
783
980
|
content = str(value or "").strip()
|
|
@@ -793,22 +990,96 @@ class GlpiContentConverter:
|
|
|
793
990
|
# 3.11.14/3.12.12/3.13.6 rescans to the end for each (quadratic).
|
|
794
991
|
head, end, tail = content.rpartition(">")
|
|
795
992
|
content = head + end + tail.replace("<", "<")
|
|
993
|
+
token = _REWRITE.set(rewrite_link)
|
|
796
994
|
try:
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
995
|
+
return _converted(content)
|
|
996
|
+
except _CallbackRaised as raised:
|
|
997
|
+
callback_error = raised.error
|
|
998
|
+
finally:
|
|
999
|
+
_REWRITE.reset(token)
|
|
1000
|
+
raise callback_error # outside the handler: the callback's, as it raised it
|
|
1001
|
+
|
|
1002
|
+
@staticmethod
|
|
1003
|
+
def document_id_of(url: str) -> int | None:
|
|
1004
|
+
"""Return the document id a URL to GLPI's document page names.
|
|
1005
|
+
|
|
1006
|
+
``None`` unless ``url`` -- relative, rooted or absolute, with any
|
|
1007
|
+
path in front -- is ``front/document.send.php`` with exactly one
|
|
1008
|
+
``docid`` parameter, in any position, made of digits.
|
|
1009
|
+
"""
|
|
1010
|
+
|
|
1011
|
+
try:
|
|
1012
|
+
parts = urlsplit(url)
|
|
1013
|
+
except ValueError: # an unclosed "[" in a host, among others
|
|
1014
|
+
return None
|
|
1015
|
+
path = parts.path
|
|
1016
|
+
if path != _DOCUMENT_PAGE and not path.endswith("/" + _DOCUMENT_PAGE):
|
|
1017
|
+
return None
|
|
1018
|
+
values = parse_qs(parts.query).get("docid", [])
|
|
1019
|
+
if len(values) != 1 or not _DOCUMENT_ID.fullmatch(values[0]):
|
|
1020
|
+
return None
|
|
1021
|
+
document_id = int(values[0])
|
|
1022
|
+
return document_id if document_id > 0 else None
|
|
1023
|
+
|
|
1024
|
+
@staticmethod
|
|
1025
|
+
def document_image(
|
|
1026
|
+
document_id: int,
|
|
1027
|
+
*,
|
|
1028
|
+
alt: str = "",
|
|
1029
|
+
itemtype: str | None = None,
|
|
1030
|
+
items_id: int | None = None,
|
|
1031
|
+
) -> str:
|
|
1032
|
+
"""Return the Markdown of an image embedding one of GLPI's documents.
|
|
1033
|
+
|
|
1034
|
+
:meth:`to_transport` renders it as GLPI's editor writes a pasted image
|
|
1035
|
+
-- the ``<img>`` inside a link to the same URL,
|
|
1036
|
+
``/front/document.send.php?docid=<document_id>``, then
|
|
1037
|
+
``&itemtype=...&items_id=...`` when given -- and :meth:`from_transport`
|
|
1038
|
+
reads that back as this same Markdown: it is spelled by the reader
|
|
1039
|
+
itself. The URL is rooted, as the editor wrote it on the instance
|
|
1040
|
+
measured; GLPI checks the reader's right to the document when it
|
|
1041
|
+
serves it.
|
|
1042
|
+
|
|
1043
|
+
Parameters
|
|
1044
|
+
----------
|
|
1045
|
+
document_id : int
|
|
1046
|
+
The document's id.
|
|
1047
|
+
alt : str, optional
|
|
1048
|
+
The image's alternative text.
|
|
1049
|
+
itemtype, items_id : str and int, optional
|
|
1050
|
+
The item the document is attached to, such as ``"Ticket"`` and
|
|
1051
|
+
its id. Both or neither.
|
|
1052
|
+
|
|
1053
|
+
Raises
|
|
1054
|
+
------
|
|
1055
|
+
GlpiValidationError
|
|
1056
|
+
``document_id`` or ``items_id`` is not a positive ``int``,
|
|
1057
|
+
``itemtype`` is not a class name, or only one of the two was
|
|
1058
|
+
given.
|
|
1059
|
+
"""
|
|
1060
|
+
|
|
1061
|
+
if not _positive(document_id):
|
|
1062
|
+
raise GlpiValidationError(f"not a GLPI document id: {document_id!r}")
|
|
1063
|
+
url = f"/{_DOCUMENT_PAGE}?docid={document_id}"
|
|
1064
|
+
if (itemtype is None) != (items_id is None):
|
|
1065
|
+
raise GlpiValidationError(
|
|
1066
|
+
"itemtype and items_id go together: give both or neither"
|
|
1067
|
+
)
|
|
1068
|
+
if itemtype is not None:
|
|
1069
|
+
if not _ITEMTYPE.fullmatch(itemtype):
|
|
1070
|
+
raise GlpiValidationError(f"not a GLPI item type: {itemtype!r}")
|
|
1071
|
+
if not _positive(items_id):
|
|
1072
|
+
raise GlpiValidationError(f"not a GLPI item id: {items_id!r}")
|
|
1073
|
+
url += f"&itemtype={itemtype}&items_id={items_id}"
|
|
1074
|
+
native = (
|
|
1075
|
+
f'<p><a href="{escape(url)}" target="_blank">'
|
|
1076
|
+
f'<img src="{escape(url)}" alt="{escape(" ".join(alt.split()))}" /></a></p>'
|
|
1077
|
+
)
|
|
1078
|
+
token = _REWRITE.set(None) # a callback in force for an enclosing read
|
|
1079
|
+
try:
|
|
1080
|
+
return html_to_markdown(native)
|
|
1081
|
+
finally:
|
|
1082
|
+
_REWRITE.reset(token)
|
|
812
1083
|
|
|
813
1084
|
@staticmethod
|
|
814
1085
|
def to_transport(value: object) -> str:
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, glpi-python-client, network access to the GLPI v2 API, and credentials allowed to read or write assets."
|
|
6
6
|
metadata:
|
|
7
7
|
package: glpi-python-client
|
|
8
|
-
version: "0.6.
|
|
8
|
+
version: "0.6.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# GLPI Asset Workflow
|
{glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-contract-workflow/SKILL.md
RENAMED
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, glpi-python-client, network access to the GLPI v2 API, and credentials allowed to read or write contracts."
|
|
6
6
|
metadata:
|
|
7
7
|
package: glpi-python-client
|
|
8
|
-
version: "0.6.
|
|
8
|
+
version: "0.6.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# GLPI Contract Workflow
|
{glpi_python_client-0.6.1 → glpi_python_client-0.6.3}/skills/glpi-document-workflow/SKILL.md
RENAMED
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, glpi-python-client, network access to the GLPI v2 API, and v1 credentials configured on the client for binary uploads."
|
|
6
6
|
metadata:
|
|
7
7
|
package: glpi-python-client
|
|
8
|
-
version: "0.6.
|
|
8
|
+
version: "0.6.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# GLPI Document Workflow
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, glpi-python-client, network access to the GLPI v2 API, and — for category writes only — a legacy v1 session (v1_base_url + v1_user_token)."
|
|
6
6
|
metadata:
|
|
7
7
|
package: glpi-python-client
|
|
8
|
-
version: "0.6.
|
|
8
|
+
version: "0.6.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# GLPI Knowledge Base
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, glpi-python-client, the GLPI Fields plugin installed server-side, and a legacy v1 session (v1_base_url + v1_user_token) — every method in this family goes over the v1 API."
|
|
6
6
|
metadata:
|
|
7
7
|
package: glpi-python-client
|
|
8
|
-
version: "0.6.
|
|
8
|
+
version: "0.6.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# GLPI Plugin Fields
|