easyvista-python-client 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/.pre-commit-config.yaml +19 -1
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/CHANGELOG.md +452 -1
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/CONTRIBUTING.md +4 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/PKG-INFO +45 -12
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/README.md +24 -6
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/api_reference.rst +32 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/conf.py +1 -5
- easyvista_python_client-0.4.0/docs/content.rst +425 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/development.rst +4 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/index.rst +1 -0
- easyvista_python_client-0.4.0/docs/installation.rst +64 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/publishing.rst +1 -1
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/user_guide.rst +166 -25
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/vendor-api-reference.md +172 -18
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/__init__.py +6 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_async/_transport.py +53 -1
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_async/client.py +296 -61
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_sync/_transport.py +53 -1
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_sync/client.py +296 -61
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_transport.py +22 -2
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_version.py +1 -1
- easyvista_python_client-0.4.0/easyvista_python_client/content/__init__.py +18 -0
- easyvista_python_client-0.4.0/easyvista_python_client/content/conversion.py +823 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/directory.py +2 -1
- easyvista_python_client-0.4.0/easyvista_python_client/exceptions.py +117 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/common.py +14 -7
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/request.py +33 -18
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/actions.py +92 -3
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/requests.py +19 -40
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/timestamps.py +19 -13
- easyvista_python_client-0.4.0/easyvista_python_client/workflow.py +337 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/pyproject.toml +42 -8
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-asset-workflow/SKILL.md +2 -2
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-client-setup/SKILL.md +20 -9
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-directory/SKILL.md +2 -2
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-document-workflow/SKILL.md +2 -2
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-instance-discovery/SKILL.md +7 -5
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-reporting-and-context/SKILL.md +2 -2
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-search-syntax/SKILL.md +2 -2
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-ticket-actions/SKILL.md +143 -23
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/easyvista-ticket-workflow/SKILL.md +82 -7
- easyvista_python_client-0.3.0/docs/installation.rst +0 -46
- easyvista_python_client-0.3.0/easyvista_python_client/exceptions.py +0 -53
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/.gitignore +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/.readthedocs.yaml +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/LICENSE +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/_static/.gitkeep +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/_templates/.gitkeep +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/docs/sponsoring.rst +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_async/__init__.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_async/_concurrency.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_fields.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_html.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_sync/__init__.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/_sync/_concurrency.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/config.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/context.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/discovery.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/field_model.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/filters.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/__init__.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/action.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/asset.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/department.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/document.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/employee.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/models/generic.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/pagination.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/py.typed +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/references.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/reporting.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/__init__.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/assets.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/departments.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/descriptor.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/discovery.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/documents.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/easyvista_python_client/resources/employees.py +0 -0
- {easyvista_python_client-0.3.0 → easyvista_python_client-0.4.0}/skills/README.md +0 -0
|
@@ -15,7 +15,25 @@ repos:
|
|
|
15
15
|
rev: v2.3.0
|
|
16
16
|
hooks:
|
|
17
17
|
- id: mypy
|
|
18
|
-
|
|
18
|
+
# The runtime dependencies, then the TYPED packages of the `content`
|
|
19
|
+
# extra. The hook's venv holds only this list, so without those four
|
|
20
|
+
# every converter base class (MarkdownConverter, MDRenderer,
|
|
21
|
+
# RenderTreeNode, MarkdownIt) resolves to Any and strict mode's
|
|
22
|
+
# disallow_subclassing_any fails content/conversion.py even though
|
|
23
|
+
# `mypy easyvista_python_client` in CI is clean. The extra's other two,
|
|
24
|
+
# cmarkgfm and mdformat-tables, ship no type information, so they are
|
|
25
|
+
# Any either way and are left out; cmarkgfm would also make every
|
|
26
|
+
# contributor without a wheel for their platform compile it before
|
|
27
|
+
# any commit. Each line here must equal the extra's requirement in
|
|
28
|
+
# pyproject; testing/test_public_api.py fails if one drifts.
|
|
29
|
+
additional_dependencies:
|
|
30
|
+
- "pydantic>=2.8"
|
|
31
|
+
- "httpx"
|
|
32
|
+
- "tenacity"
|
|
33
|
+
- "beautifulsoup4>=4.15"
|
|
34
|
+
- "markdown-it-py>=3.0,<4"
|
|
35
|
+
- "markdownify>=1.2.3,<1.3"
|
|
36
|
+
- "mdformat>=0.7.22,<0.8"
|
|
19
37
|
# Must mirror [tool.mypy] exclude in pyproject.toml. pre-commit passes
|
|
20
38
|
# filenames explicitly, and mypy ignores its own `exclude` for files named
|
|
21
39
|
# on the command line -- so this regex is the ONLY thing keeping the hook
|
|
@@ -15,6 +15,456 @@ is the error. Tags carry no `v` prefix.
|
|
|
15
15
|
|
|
16
16
|
## [Unreleased]
|
|
17
17
|
|
|
18
|
+
## [0.4.0] - 2026-10-02
|
|
19
|
+
|
|
20
|
+
Adds Markdown <-> memo HTML conversion as an optional extra, drops Python
|
|
21
|
+
3.10, and **stops a ticket's workflow changing by accident -- which is a
|
|
22
|
+
breaking release.** `set_status` is gone (it was the vendor close request under
|
|
23
|
+
a name that hid it), `close_ticket` requires an explicit opt-in, and every
|
|
24
|
+
write the package can tell may change a ticket's workflow is refused before it
|
|
25
|
+
is sent unless the call says so. What it cannot tell is not covered (see
|
|
26
|
+
*Notes*).
|
|
27
|
+
|
|
28
|
+
A `0.4.0` section was first prepared on 2026-09-30 around a different
|
|
29
|
+
converter. It was never tagged or uploaded, and this section replaces it;
|
|
30
|
+
`### Changed since the 2026-09-30 preparation` says what moved, for anyone who
|
|
31
|
+
built against that branch.
|
|
32
|
+
|
|
33
|
+
**Upgrading.** **Python 3.10 is no longer supported** (see `### Removed`). On
|
|
34
|
+
3.10, pip keeps resolving `0.3.0`, the last release that installs there. The
|
|
35
|
+
converter is purely additive: the core package imports none of the extra's
|
|
36
|
+
dependencies. Every other breaking change is in the workflow guard, and each is
|
|
37
|
+
marked `**BREAKING**` in `### Changed` or `### Removed`. Read `### Upgrading`
|
|
38
|
+
below.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **The workflow guard.** `WorkflowEffect` (`INTERRUPTS`, `ADVANCES`,
|
|
43
|
+
`UNKNOWN`) and `EasyvistaWorkflowEffectRefused`, both exported at the package
|
|
44
|
+
root, and `easyvista_python_client.workflow` (`workflow_triggers`,
|
|
45
|
+
`classify_workflow_effects`), which names what a write may do to a ticket's
|
|
46
|
+
workflow. `EasyvistaWorkflowEffectRefused` is a `ValueError`, **not** an
|
|
47
|
+
`EasyvistaError`: the refused write is never sent, so there is no status code
|
|
48
|
+
and nothing transient, and it carries `effects` and `triggers` (what named
|
|
49
|
+
them). A ticket's status follows its workflow -- "Advancing through the steps
|
|
50
|
+
of a workflow changes the status of a ticket." (tier 1) -- so a write that
|
|
51
|
+
touches workflow state is not a bookkeeping write.
|
|
52
|
+
- An `allow_workflow_effect=` keyword, taking one `WorkflowEffect` or an
|
|
53
|
+
iterable of them, on `send`, `update_ticket`, `create_action`, `create_task`,
|
|
54
|
+
`update_action` and `end_action` (default: allows nothing), and **required**
|
|
55
|
+
on `close_ticket`. `RequestSpec.allow_workflow_effect` and
|
|
56
|
+
`RequestSpec.allowing()` carry the same opt-in on a request spec.
|
|
57
|
+
- `resources.actions.build_get_action(..., fields=...)` projects the item read,
|
|
58
|
+
as the list builders already did.
|
|
59
|
+
- `reassign_action(action_id, *, group_id=None, done_by_id=None)` on both
|
|
60
|
+
clients, with `resources.actions.build_reassign_action`: reassign an action
|
|
61
|
+
(for example, escalate the open workflow step to another group) without
|
|
62
|
+
ending it. It is not refused by the workflow guard. The vendor documents no
|
|
63
|
+
reassignment route (tier 1), so the effect was measured: 2026-10-02, one
|
|
64
|
+
instance, two tickets, so it may not generalise -- the group was stored, the
|
|
65
|
+
step stayed open, the ticket's status did not move and no new action rows
|
|
66
|
+
appeared; the ticket's owning group was read on one of the two tickets and
|
|
67
|
+
did not follow the action's. The person write (`done_by_id`) is unmeasured.
|
|
68
|
+
- `easyvista_python_client.content.EasyvistaContentConverter`, behind the new
|
|
69
|
+
optional extra `easyvista-python-client[content]`. It has two static
|
|
70
|
+
methods. `from_transport(value, *, plain_text_is_markdown=False)` reads a
|
|
71
|
+
memo, rich-text HTML or plain text, as Markdown. `to_transport(value)`
|
|
72
|
+
renders Markdown as the HTML a memo is written with.
|
|
73
|
+
|
|
74
|
+
**The Markdown is CommonMark with GFM tables.** Writing renders it with
|
|
75
|
+
`cmark-gfm`, with only its `table` extension: a newline is a line break, and
|
|
76
|
+
raw HTML passes through. Reading converts HTML with `markdownify`, then
|
|
77
|
+
re-renders the result with `mdformat`, which keeps only the escapes
|
|
78
|
+
CommonMark needs.
|
|
79
|
+
|
|
80
|
+
**Text in a memo is literal, and the Markdown spells it so.** A typed
|
|
81
|
+
`__init__` reads as `\_\_init\_\_`, `\\serveur` as `\\\serveur`, `# titre`
|
|
82
|
+
at a line start as `\# titre`, and `<Entrée>` as `\<Entrée>`. Ordinary prose
|
|
83
|
+
such as `fichier_de_test_v2.xlsx`, `C:\Temp`, `R&D` or `Ticket #4521` comes
|
|
84
|
+
back as typed. The aim is that rendering the Markdown displays what the
|
|
85
|
+
memo displayed, and that reading that back gives the same Markdown; the
|
|
86
|
+
measurements below say how far that held, and `docs/content.rst` lists the
|
|
87
|
+
shapes where it does not.
|
|
88
|
+
|
|
89
|
+
What else a reader sees:
|
|
90
|
+
- A memo with no HTML element is read as literal lines, a hard break per
|
|
91
|
+
line. Pass `plain_text_is_markdown=True` for a value that is your own
|
|
92
|
+
Markdown: it then passes through, stripped, unless it starts with `<` and
|
|
93
|
+
holds a real HTML element anywhere. So Markdown opening with an autolink
|
|
94
|
+
and carrying inline HTML further on is read as HTML, and loses the
|
|
95
|
+
autolink.
|
|
96
|
+
- A memo holding one real HTML element is read as HTML throughout, so
|
|
97
|
+
Markdown syntax beside it is kept as literal characters. The obsolete
|
|
98
|
+
elements (`<font>`, `<center>`, ...) count as HTML.
|
|
99
|
+
- Underline, highlight and strike, which CommonMark cannot spell, are kept
|
|
100
|
+
as raw `<u>`, `<mark>`, `<ins>` and `<s>`.
|
|
101
|
+
- A table nested in a table is flattened to its words, every one kept and in
|
|
102
|
+
order, and a table without a header row gains an empty one.
|
|
103
|
+
- Nested lists nest and keep their numbers. `<script>`, `<style>`,
|
|
104
|
+
`<template>` and `<title>` bodies are dropped.
|
|
105
|
+
|
|
106
|
+
Measured 2026-10-02 on 367 memos read from one preproduction instance on
|
|
107
|
+
2026-10-01 (tier 4, one instance, may not generalise): no memo changed a
|
|
108
|
+
word, and 367 of 367 readings were fixed points. 65 of them displayed
|
|
109
|
+
differently from the memo, each in a documented way that keeps its words:
|
|
110
|
+
in 62 a table gained an empty header row (61 of them a notification
|
|
111
|
+
template's flattened nested table), and 3 had no HTML element and were
|
|
112
|
+
read as literal lines. Synthetic shapes do lose words: a table row longer
|
|
113
|
+
than the first, a `|` in a cell's code or link, an image title holding a
|
|
114
|
+
`"`, and others that `docs/content.rst` lists.
|
|
115
|
+
|
|
116
|
+
It is a port of `glpi_python_client`'s `content/conversion.py` at `917f030`,
|
|
117
|
+
plus 15 fixes listed under `### Notes`. **The two should move together.**
|
|
118
|
+
|
|
119
|
+
Why it is here: until now this package could only strip HTML to plain text,
|
|
120
|
+
so a downstream GLPI-to-EasyVista sync wrote its own converter. Measured
|
|
121
|
+
2026-09-30 (tier 4, one instance), a GLPI description holding a pasted URL
|
|
122
|
+
and a titled link reached EasyVista with neither link clickable. EasyVista
|
|
123
|
+
stored exactly the HTML it was sent. That converter had escaped `<` a
|
|
124
|
+
second time, fused the link title into the `href`, cut a URL at its first
|
|
125
|
+
`)`, and would read two lone asterisks as emphasis. This converter handles
|
|
126
|
+
each of those correctly (checked 2026-10-02 on synthetic equivalents): a
|
|
127
|
+
`<` is escaped once, a link title stays a `title`, a URL keeps its
|
|
128
|
+
parentheses, and a lone `*` stays literal.
|
|
129
|
+
|
|
130
|
+
Two properties are worth knowing before relying on it:
|
|
131
|
+
- **Deep nesting degrades to text.** The conversion is attempted, and a
|
|
132
|
+
`RecursionError` is answered with the memo's words. From a shallow stack,
|
|
133
|
+
the deepest `<div>` document that still converts with its structure is 245
|
|
134
|
+
levels on CPython 3.11 and 327 on 3.12–3.14 (measured 2026-10-02). A caller
|
|
135
|
+
already within about 25 frames of the recursion limit still gets
|
|
136
|
+
`EasyvistaContentError`, and within the last few frames, a bare
|
|
137
|
+
`RecursionError`.
|
|
138
|
+
- **It is not a sanitiser.** Raw HTML, `javascript:` link targets and
|
|
139
|
+
`<javascript:...>` autolinks in the Markdown all go out live, and reading
|
|
140
|
+
keeps a memo's `javascript:` links. A caller relaying Markdown it did not
|
|
141
|
+
write must neutralise raw HTML and executable schemes. Text a memo
|
|
142
|
+
*displays* as markup, such as `<script>`, reads back escaped, as the
|
|
143
|
+
text it is.
|
|
144
|
+
|
|
145
|
+
Importing `easyvista_python_client.content` without the extra, or with any
|
|
146
|
+
one of its six packages missing, raises an `ImportError` naming
|
|
147
|
+
`pip install "easyvista-python-client[content]"`.
|
|
148
|
+
- `EasyvistaContentError`, a subclass of `EasyvistaError` exported at the
|
|
149
|
+
package root. It is raised for any converter fault the text fallback does
|
|
150
|
+
not absorb, with the underlying exception as `__cause__`. It lives in the
|
|
151
|
+
core package, so it can be caught whether or not the extra is installed.
|
|
152
|
+
- The `dev` and `docs` extras now install the `content` extra's packages, with
|
|
153
|
+
the same bounds, since CI runs the converter's tests and the API reference
|
|
154
|
+
imports it. `testing/test_public_api.py` fails if the copies drift, or if
|
|
155
|
+
its list of the extra's import names stops matching `pyproject.toml`.
|
|
156
|
+
|
|
157
|
+
### Changed
|
|
158
|
+
|
|
159
|
+
- **BREAKING** `close_ticket` requires `allow_workflow_effect=`, keyword-only
|
|
160
|
+
with no default: leaving it out is a `TypeError`, and a value that does not
|
|
161
|
+
include `WorkflowEffect.INTERRUPTS` is refused with
|
|
162
|
+
`EasyvistaWorkflowEffectRefused` before any request is made. The vendor
|
|
163
|
+
documents the close request as interrupting the workflow (tier 1, see
|
|
164
|
+
*Removed*), so the call site now says that it means to.
|
|
165
|
+
- **BREAKING** `end_action` is guarded. Unless `allow_workflow_effect` includes
|
|
166
|
+
`WorkflowEffect.ADVANCES` it first reads the action (one item read projecting
|
|
167
|
+
`ACTION_ID` and `WORKFLOW_ID`) and refuses, with no end request sent, when
|
|
168
|
+
the action is a workflow step (`WORKFLOW_ID` set), when the record comes back
|
|
169
|
+
without a `WORKFLOW_ID` column (which cannot be told from a step), or when
|
|
170
|
+
the read returns a different `ACTION_ID` than the one asked for. `end_all=True`
|
|
171
|
+
always needs `ADVANCES`. Ending an action you created yourself needs no
|
|
172
|
+
opt-in (see *Notes*). If the read fails, its error propagates and nothing is ended.
|
|
173
|
+
Ending a workflow step moves the workflow on -- vendor-documented only by the
|
|
174
|
+
UI's Finish wizard, and measured on one instance on 2026-09-01 (2 of 2), so
|
|
175
|
+
it may not generalise.
|
|
176
|
+
- **BREAKING** `end_action`'s explicit `action_id` must be a positive integer.
|
|
177
|
+
`0`, negatives, blanks, RFC numbers, floats and booleans now raise
|
|
178
|
+
`ValueError` before any request, and a numeric string is sent as an integer
|
|
179
|
+
(`"123"` goes out as `123`). `action_id=None` is still refused unless
|
|
180
|
+
`end_all=True`.
|
|
181
|
+
- **BREAKING** `update_ticket`, `update_action`, `create_action`, `create_task`
|
|
182
|
+
and `send` refuse a body or route that may change the workflow unless the call
|
|
183
|
+
passes `allow_workflow_effect=`. Named: the workflow-control bodies `closed`,
|
|
184
|
+
`end_action`, `suspended` and `restarted` as a top-level key in any casing on
|
|
185
|
+
any path; on a `requests/{rfc}` route, the status, catalog and parent-request
|
|
186
|
+
columns and a `DELETE`; on an existing `actions/{id}` route, the end date,
|
|
187
|
+
type, parent, ticket and workflow, stage and step columns (creating an action
|
|
188
|
+
or a task names a narrower set); a write to `actions/<x>` where `<x>` is not an
|
|
189
|
+
integer id, which is the vendor's end-action route `PUT
|
|
190
|
+
actions/{rfc_number}`, whatever the body says; every ticket sub-route that is
|
|
191
|
+
a command rather than a record (`close`, `suspend`, `restart`,
|
|
192
|
+
`workflowstart`, ...) and `requests/without-workflow`. The column rules apply
|
|
193
|
+
to the `requests/` and `actions/` routes only: a route of any other family is
|
|
194
|
+
not classified by column. What the typed models declare needs no opt-in,
|
|
195
|
+
and neither do text, owner, group, done-by, impact or urgency. The exact
|
|
196
|
+
lists are in `docs/vendor-api-reference.md`, "Ticket workflow". **This is a
|
|
197
|
+
deny-list, and a deny-list of columns cannot be complete**: what is not named
|
|
198
|
+
is unclassified, not proven neutral.
|
|
199
|
+
- **BREAKING** `update_action` refuses an action id that is not a positive
|
|
200
|
+
integer, `None` included: `PUT actions/{rfc_number}` is the end-action route
|
|
201
|
+
on the same path, so an RFC number would not edit an action.
|
|
202
|
+
- **BREAKING** The spec builders changed with the guard. A spec from
|
|
203
|
+
`resources.requests.build_close_ticket` or `resources.actions.build_end_action`
|
|
204
|
+
names a workflow effect, so this package's transport refuses it until it is
|
|
205
|
+
passed through `.allowing(...)`; code that builds one and sends it itself must
|
|
206
|
+
say so. And `resources.actions.build_end_action` and `build_update_action`
|
|
207
|
+
now raise `ValueError` at build time for an action id that is not a positive
|
|
208
|
+
integer, where they formerly passed the id through as given.
|
|
209
|
+
- **BREAKING** `send()` -- the path every typed method and the client's own
|
|
210
|
+
`send` go through -- refuses outright, with `ValueError` and whatever the
|
|
211
|
+
method or opt-in, a path containing a dot segment (`.` or `..`), a
|
|
212
|
+
percent-encoded slash or backslash, or a raw backslash: the HTTP client
|
|
213
|
+
collapses a dot segment, and a server may read the others as a separator, so
|
|
214
|
+
the request could reach a route other than the one that was checked. No API
|
|
215
|
+
route needs one. Whether this server reads them as separators is not
|
|
216
|
+
measured; the check fails closed. Document downloads (`get_bytes`,
|
|
217
|
+
`stream_bytes`) are reads and are never gated.
|
|
218
|
+
- **BREAKING** A request is treated as a read only when its method **and** the
|
|
219
|
+
value of every method-override header (`X-HTTP-Method-Override`,
|
|
220
|
+
`X-HTTP-Method`, `X-Method-Override`) on the request are reads, so such a
|
|
221
|
+
header cannot hide a write behind a `GET`: a request whose override header
|
|
222
|
+
names a write is classified as that write, and refused if it names a workflow
|
|
223
|
+
effect and the call did not allow it, whatever method it is sent as. The
|
|
224
|
+
headers read are the ones that go on the wire, `config.extra_headers` with
|
|
225
|
+
the request's own laid over them. Whether this API honours these headers is
|
|
226
|
+
not recorded.
|
|
227
|
+
- **BREAKING** A write that names a workflow effect and is allowed is sent
|
|
228
|
+
**once**, never retried: `close_ticket` and `end_action` formerly retried a
|
|
229
|
+
429, a 5xx or a connection error when `max_retries` was above its default of
|
|
230
|
+
`0`, and now raise after the first attempt. Each close inserts another
|
|
231
|
+
anticipated closing action and each end ends whatever is open, so a resend
|
|
232
|
+
after a lost response is not a repeat of the same request. This includes
|
|
233
|
+
`end_action` on your own action. Every other request keeps its retries.
|
|
234
|
+
|
|
235
|
+
### Removed
|
|
236
|
+
|
|
237
|
+
- **BREAKING** — Python 3.10. `requires-python` is now `>=3.11`, the 3.10
|
|
238
|
+
classifier is gone, and the CI and release matrices run 3.11–3.14. CPython
|
|
239
|
+
3.10 reaches end of life in October 2026 (PEP 619), and `glpi_python_client`,
|
|
240
|
+
whose converter `content` ports, dropped it in `524304a`. Two requirements
|
|
241
|
+
that existed only for 3.10 go with it: `typing-extensions` (a runtime
|
|
242
|
+
dependency on 3.10 only) and `tomli` (in `dev` and `docs`). Nothing moves on
|
|
243
|
+
3.11 and later. The timestamp parser keeps the normalisation it carried for
|
|
244
|
+
3.10, so it accepts and refuses the same values as before.
|
|
245
|
+
- **BREAKING** `EasyvistaClient.set_status` / `AsyncEasyvistaClient.set_status`
|
|
246
|
+
and `resources.requests.build_set_status`. They sent the vendor CLOSE request,
|
|
247
|
+
which the vendor close page documents (tier 1, re-read 2026-10-02) as
|
|
248
|
+
interrupting the workflow, setting the final status, deleting the unfinished
|
|
249
|
+
actions when `delete_actions` is set (otherwise, by the package's reading of
|
|
250
|
+
the page, ending them) and inserting an anticipated closing action, none of it
|
|
251
|
+
conditional on the status sent. The page documents final statuses only, so
|
|
252
|
+
for a non-final one that is an extrapolation it neither exempts nor covers.
|
|
253
|
+
A synchroniser that used `set_status` to mirror an intermediate status closed
|
|
254
|
+
tickets early. The root cause was established on 2026-10-01/02 from the
|
|
255
|
+
synchroniser's code (it sent the close request right after every create and on
|
|
256
|
+
every status push) and from the vendor page; the drain of the open workflow
|
|
257
|
+
action across such a write was measured on one ticket (2026-09-01, one
|
|
258
|
+
instance, tier 4, so it may not generalise). The vendor documents no status
|
|
259
|
+
setter and this package has none: a flat status update is excluded from the
|
|
260
|
+
vendor's ticket update body (tier 1) and was seen to return 200 while dropping
|
|
261
|
+
the status (0.2.0 entry below), and a ticket's status follows its workflow.
|
|
262
|
+
`close_ticket` and `resources.requests.build_close_ticket` remain.
|
|
263
|
+
|
|
264
|
+
### Changed since the 2026-09-30 preparation
|
|
265
|
+
|
|
266
|
+
None of this reached PyPI, so it is not a break for anyone upgrading from
|
|
267
|
+
`0.3.0`. It matters to a caller who built against the branch.
|
|
268
|
+
|
|
269
|
+
- **The converter changed design.** The first preparation ported
|
|
270
|
+
`glpi_python_client` at `4fc3bed`, built on python-markdown with its
|
|
271
|
+
`nl2br`, `sane_lists`, `fenced_code` and `tables` extensions.
|
|
272
|
+
`glpi_python_client` replaced that design in `917f030` with markdownify +
|
|
273
|
+
mdformat inbound and cmark-gfm outbound, and this package follows it.
|
|
274
|
+
- **The `content` extra's dependencies changed.** `markdown` is gone, and
|
|
275
|
+
`cmarkgfm`, `markdown-it-py`, `mdformat` and `mdformat-tables` are new. The
|
|
276
|
+
bounds are now `beautifulsoup4>=4.15`, `cmarkgfm>=2025.10.22`,
|
|
277
|
+
`markdown-it-py>=3.0,<4`, `markdownify>=1.2.3,<1.3`, `mdformat>=0.7.22,<0.8`
|
|
278
|
+
and `mdformat-tables>=1.0,<1.1`. The upper bounds are deliberate, because
|
|
279
|
+
the reader overrides private surfaces of `markdownify` and `mdformat`.
|
|
280
|
+
`docs/content.rst` gives the reason for each bound.
|
|
281
|
+
- **The Markdown dialect is CommonMark with GFM tables**, not
|
|
282
|
+
python-markdown's. What a caller sees:
|
|
283
|
+
- A fence's language tag now survives a round trip.
|
|
284
|
+
- Strong inside emphasis keeps its bold.
|
|
285
|
+
- Two `<br>` in a row stay two line breaks.
|
|
286
|
+
- A `<pre>` opening a list item stays code.
|
|
287
|
+
- Struck text is kept as a raw `<s>` instead of losing the line.
|
|
288
|
+
- `~~text~~` is literal tildes on write, as before.
|
|
289
|
+
- `<javascript:alert(1)>` now becomes a live link, where python-markdown left
|
|
290
|
+
it as raw markup.
|
|
291
|
+
- **Escapes differ.** A displayed `<` reads as `\<` rather than `<`. `#4521`
|
|
292
|
+
at a line start is no longer escaped, since CommonMark needs a space after
|
|
293
|
+
`#`. A hard break reads as a trailing backslash rather than two spaces. Link
|
|
294
|
+
targets are kept in `<...>` where they need it, and a `[` or `]` in one comes
|
|
295
|
+
back percent-encoded.
|
|
296
|
+
- **Plain text is read as literal lines.** A memo with no HTML element used to
|
|
297
|
+
pass through verbatim, so its text was read as Markdown and a typed
|
|
298
|
+
`__init__` rendered bold. It is now escaped, and each line kept with a hard
|
|
299
|
+
break. How EasyVista's UI displays such a memo is unverified (`O-MEMOFORMAT`).
|
|
300
|
+
`plain_text_is_markdown=True` restores pass-through for your own Markdown.
|
|
301
|
+
- **The depth cliff moved.** With the old design it was measured at 493 levels
|
|
302
|
+
on CPython 3.12–3.14. With this one it is 327 there, and 245 on 3.11.
|
|
303
|
+
- **The `beautifulsoup4` workaround is gone.** The old design rewrote
|
|
304
|
+
self-closing void tags to dodge a defect that dropped the text after a
|
|
305
|
+
`<br />` in a body that also used `<br>`. That defect no longer reproduced on
|
|
306
|
+
4.15.0 (measured 2026-09-30), so the floor is now 4.15 and the workaround is
|
|
307
|
+
removed.
|
|
308
|
+
|
|
309
|
+
### Upgrading
|
|
310
|
+
|
|
311
|
+
- `client.set_status(rfc, status_guid=g)` -- delete it; nothing replaces it,
|
|
312
|
+
because the vendor documents no status setter (a flat status update is
|
|
313
|
+
excluded from its ticket update body, and was seen to drop the status -- see
|
|
314
|
+
0.2.0) and a ticket's status follows its workflow. To move a ticket through
|
|
315
|
+
its workflow, end the step's open action with
|
|
316
|
+
`end_action(rfc, action_id=..., allow_workflow_effect=WorkflowEffect.ADVANCES)`.
|
|
317
|
+
The status that follows is the workflow's, not yours to choose. This is not
|
|
318
|
+
documented on the REST page, which is silent about the workflow; it was
|
|
319
|
+
measured on one instance on 2026-09-01 (2 of 2 tickets) and may not
|
|
320
|
+
generalise, so re-read the ticket afterwards. To close, call
|
|
321
|
+
`close_ticket(rfc, allow_workflow_effect=WorkflowEffect.INTERRUPTS, status_guid=g)`.
|
|
322
|
+
- `end_action` callers: pass the integer id of an action you read, never an RFC
|
|
323
|
+
number, `0` or a blank. Ending your own action still needs no opt-in (see
|
|
324
|
+
*Notes*), and now costs one extra item read. Ending a workflow step, or any action whose record
|
|
325
|
+
shows no `WORKFLOW_ID`, needs `WorkflowEffect.ADVANCES`, and so does
|
|
326
|
+
`end_all=True`.
|
|
327
|
+
- Code that puts a status, catalog or other named column into `extra_payload`,
|
|
328
|
+
or calls `send` with a workflow route or body, now raises until it passes
|
|
329
|
+
`allow_workflow_effect=`. `WorkflowEffect.UNKNOWN` means undocumented, not
|
|
330
|
+
harmless: read "Ticket workflow" in `docs/vendor-api-reference.md` first.
|
|
331
|
+
- Code that builds a close or end-action spec with `build_close_ticket` or
|
|
332
|
+
`build_end_action` and sends it through the transport itself: pass the spec
|
|
333
|
+
through `.allowing(...)` first, and pass `build_end_action` and
|
|
334
|
+
`build_update_action` an integer action id, never an RFC number.
|
|
335
|
+
- Catch `EasyvistaWorkflowEffectRefused` (or `ValueError`) where you record
|
|
336
|
+
per-record failures. It is a `ValueError`, **not** an `EasyvistaError`, and
|
|
337
|
+
carries no status code; it is never transient. Code that catches
|
|
338
|
+
`EasyvistaError` around a close for cleanup will NOT catch it. `ValueError`
|
|
339
|
+
also catches the other local refusals above.
|
|
340
|
+
- If you set `max_retries` above its default of `0`, a lost response to an
|
|
341
|
+
allowed workflow write now surfaces as an error instead of a silent resend.
|
|
342
|
+
Re-read the ticket before repeating it.
|
|
343
|
+
- The minor bump is deliberate: a dependant pinned `>=0.3.0,<0.4` does **not**
|
|
344
|
+
pick this up, and must widen its constraint on purpose.
|
|
345
|
+
|
|
346
|
+
### Documentation
|
|
347
|
+
|
|
348
|
+
- `docs/vendor-api-reference.md` gains "Ticket workflow": what each documented
|
|
349
|
+
write does to a workflow, with the vendor page quoted (tier 1), the guard's
|
|
350
|
+
deny-list exactly as the code holds it, and the measurements labelled tier 4
|
|
351
|
+
with their instance and date. O-CLOSE-DEFAULT is closed at tier 1: the vendor
|
|
352
|
+
close page documents an omitted `status_GUID` as defaulting to the Closed
|
|
353
|
+
meta-status. It was not measured here.
|
|
354
|
+
- The README, the user guide, the API reference and the `easyvista-client-setup`,
|
|
355
|
+
`easyvista-ticket-actions` and `easyvista-ticket-workflow` skills describe the
|
|
356
|
+
guard; the `easyvista-instance-discovery` skill now says `close_ticket` stops
|
|
357
|
+
the workflow and is not a way to pick an intermediate status. The
|
|
358
|
+
ticket-workflow skill's first gotcha is now that `close_ticket` is not a
|
|
359
|
+
status setter.
|
|
360
|
+
- The `PostRequest.workflow_start` docstring records that the flag is a no-op
|
|
361
|
+
(tier 4: two tickets identical but for it came back byte-identical, 2026-09-01,
|
|
362
|
+
one instance), so `workflow_start=False` does not create a ticket without its
|
|
363
|
+
workflow. The vendor create page documents no such parameter and states that
|
|
364
|
+
the workflow is started; the workflow-less create is the virtual-agent route
|
|
365
|
+
`requests/without-workflow`, which the guard refuses unless allowed.
|
|
366
|
+
- **Retracted:** that `set_status` reaches every status, as the 0.2.0 entry
|
|
367
|
+
below put it ("a fresh ticket landed on exactly the status requested every
|
|
368
|
+
time, non-terminal ones included") and the `RequestUpdate` docstring repeated
|
|
369
|
+
it ("That route reaches **every** status, not just terminal ones"). Six status
|
|
370
|
+
GUIDs were tried and each landed, but that was measured by re-reading the
|
|
371
|
+
status only: the measurement never looked at the workflow or the ticket's
|
|
372
|
+
open actions, and the vendor close page documents the close request as
|
|
373
|
+
interrupting the workflow (tier 1). A status that landed is not evidence that
|
|
374
|
+
nothing else moved, and that finding was read as a safe status setter, which
|
|
375
|
+
it was not. The 0.2.0 section is left as written.
|
|
376
|
+
- `docs/content.rst` is a user-guide page for the converter. It covers what a
|
|
377
|
+
memo holds, what each direction reads and writes, what text is escaped and
|
|
378
|
+
why, and what survives a round trip and what is lost. It also covers what
|
|
379
|
+
the converter does not sanitise, the dependency bounds, and the
|
|
380
|
+
CVE-2025-6069 guard. The API reference gains a "Rich-text content" section.
|
|
381
|
+
- `docs/vendor-api-reference.md` records the memo format as tier 4 (above),
|
|
382
|
+
and the vendor's MEMO and TEXT AREA form objects as tier 1
|
|
383
|
+
(`docs.easyvista.com/docs/form`, read 2026-10-02). That page does not say
|
|
384
|
+
which object a ticket's or an action's memo is. A second tier-1 page
|
|
385
|
+
(`docs.easyvista.com/docs/service-manager-comment-log-creation`, read
|
|
386
|
+
2026-10-02) sets a custom comment-log field on the request form to "Text
|
|
387
|
+
area", which leans towards HTML display but is not about the built-in
|
|
388
|
+
memos. It also opens `O-MEMOFORMAT` for what is not yet known:
|
|
389
|
+
- how the UI displays a memo with no HTML element;
|
|
390
|
+
- what the web UI's own editor writes;
|
|
391
|
+
- how the UI treats the newlines between blocks;
|
|
392
|
+
- what it shows for raw markup;
|
|
393
|
+
- whether an empty table header row is visible;
|
|
394
|
+
- whether a percent-encoded link target still resolves.
|
|
395
|
+
- The `easyvista-ticket-workflow` and `easyvista-ticket-actions` skills each
|
|
396
|
+
gain a gotcha: a memo stores what it is sent, nothing renders Markdown for
|
|
397
|
+
you, and the converter's dialect is CommonMark with GFM tables.
|
|
398
|
+
- `docs/installation.rst`, `docs/development.rst`, `docs/publishing.rst`,
|
|
399
|
+
`README.md`, `CONTRIBUTING.md` and every skill's `compatibility` line now
|
|
400
|
+
state the Python 3.11 floor.
|
|
401
|
+
|
|
402
|
+
### Notes
|
|
403
|
+
|
|
404
|
+
- What the workflow guard does not establish. It is a deny-list: the vendor's
|
|
405
|
+
update pages accept every column of the ticket and action tables except an
|
|
406
|
+
excluded list (tier 1), and a per-instance business rule can fire on
|
|
407
|
+
any write, so a write the guard does not name is unclassified, not proven
|
|
408
|
+
neutral. `end_action` tells a workflow step from your own action by
|
|
409
|
+
`WORKFLOW_ID` (1500 of 1500 rows, 2026-09-02, one instance); whether an
|
|
410
|
+
action created under a step by `create_action` carries one is unmeasured, and
|
|
411
|
+
if it does, ending it is refused too -- the safe direction. The live checks
|
|
412
|
+
in `integration_tests/test_live_workflow_guard.py` are gated: its first two
|
|
413
|
+
tests read only, and the rest write and run only when
|
|
414
|
+
`EASYVISTA_TEST_RUN_WORKFLOW_CENSUS=1` is set.
|
|
415
|
+
- **The 15 fixes on top of `glpi_python_client` `917f030`**, measured
|
|
416
|
+
together on this package's synthetic corpus and on the 367-memo sample, and
|
|
417
|
+
still to be proposed to `glpi_python_client`:
|
|
418
|
+
1. Escapes and character references in an image's alt text are kept.
|
|
419
|
+
markdown-it-py 3 leaves them as `text_special` tokens, which mdformat
|
|
420
|
+
rendered as nothing.
|
|
421
|
+
2. A paragraph line opening with `~~~` is escaped, because CommonMark reads
|
|
422
|
+
it as a fence.
|
|
423
|
+
3. A `!` that ends a text before a link is escaped, so it does not turn the
|
|
424
|
+
link into an image.
|
|
425
|
+
4. An alt text starting with `^` is escaped, because cmark-gfm never opens
|
|
426
|
+
an image on `![^`.
|
|
427
|
+
5. A `<` that mdformat leaves bare after an escaped one is escaped, so
|
|
428
|
+
`<<a@b.c>` no longer becomes an autolink.
|
|
429
|
+
6. A `<br>` inside inline code (`<code>`, `<kbd>`, `<samp>`) splits the
|
|
430
|
+
span, and a `|` in such a span in a table cell is escaped.
|
|
431
|
+
7. A table inside a heading or a link is flattened, as one inside a cell
|
|
432
|
+
already was.
|
|
433
|
+
8. A block inside a flattened cell stays on its link's line.
|
|
434
|
+
9. A flattened cell's edge counts as a space when deciding whether `**`
|
|
435
|
+
closes.
|
|
436
|
+
10. `<center>` is a block, as `<div>` is, except inside `<pre>`.
|
|
437
|
+
11. `<u>`, `<mark>` and `<ins>` are kept as raw tags.
|
|
438
|
+
12. Ordered items are numbered in linear time, where counting every earlier
|
|
439
|
+
item was quadratic, and `start` is read with `isdecimal`.
|
|
440
|
+
13. The pattern that splits emphasis and links into edges and content is
|
|
441
|
+
linear on runs of spaces or `<br>`. It was quadratic.
|
|
442
|
+
14. A `colspan` or `start` that is not a decimal number, such as `"²"` or
|
|
443
|
+
5,000 digits, degrades the memo to text instead of failing it.
|
|
444
|
+
15. Every `<` after a memo's last `>` is read as text. CPython's
|
|
445
|
+
`html.parser` before 3.11.14, 3.12.12 and 3.13.6 is quadratic on a run
|
|
446
|
+
of unfinished tags there (CVE-2025-6069, python/cpython#135462).
|
|
447
|
+
- **What does not round-trip is documented, not hidden.** `docs/content.rst`
|
|
448
|
+
lists both sides.
|
|
449
|
+
- Markdown written and read back comes back in mdformat's canonical
|
|
450
|
+
spelling. Text in angle brackets that is not a URL is lost. An e-mail
|
|
451
|
+
autolink becomes a `mailto:` link, and `[`/`]` in a link target come back
|
|
452
|
+
percent-encoded.
|
|
453
|
+
- A memo read and written back kept every word on the 367-memo sample. A
|
|
454
|
+
nested table's grid becomes text in its cell, and a header-less table
|
|
455
|
+
gains an empty header row. The known holes, all synthetic and absent
|
|
456
|
+
from the sample, are listed too, the ones that lose words among them.
|
|
457
|
+
|
|
458
|
+
After one write-and-read cycle, a further one changes nothing more.
|
|
459
|
+
- `cmarkgfm` 2025.10.22 bundles cmark-gfm `0.29.0.gfm.13`. PyPI lists no wheel
|
|
460
|
+
of that release for macOS x86_64 or Windows ARM64 (read 2026-10-02). Unless a
|
|
461
|
+
later release adds one, it builds from source there, which needs a C
|
|
462
|
+
compiler.
|
|
463
|
+
- mdformat 1.x, markdown-it-py 4.x and mdformat-gfm 1.0 were tried and are not
|
|
464
|
+
adopted. markdown-it-py 4 caps the cells it fills in, which ends a ragged
|
|
465
|
+
table early and turns the rest into literal pipes. Re-measure before raising
|
|
466
|
+
those bounds.
|
|
467
|
+
|
|
18
468
|
## [0.3.0] - 2026-09-02
|
|
19
469
|
|
|
20
470
|
Makes the task-vs-action distinction inspectable. A GLPI comment corresponds to
|
|
@@ -1142,7 +1592,8 @@ Initial public release.
|
|
|
1142
1592
|
status/error code, with non-retryable validation errors (HTTP 590, code 2013).
|
|
1143
1593
|
- `py.typed` marker — the package ships inline type information.
|
|
1144
1594
|
|
|
1145
|
-
[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.
|
|
1595
|
+
[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.0...HEAD
|
|
1596
|
+
[0.4.0]: https://github.com/baraline/easyvista_python_client/compare/0.3.0...0.4.0
|
|
1146
1597
|
[0.3.0]: https://github.com/baraline/easyvista_python_client/compare/0.2.0...0.3.0
|
|
1147
1598
|
[0.2.0]: https://github.com/baraline/easyvista_python_client/compare/0.1.0...0.2.0
|
|
1148
1599
|
[0.1.0]: https://github.com/baraline/easyvista_python_client/releases/tag/0.1.0
|
|
@@ -8,6 +8,10 @@ this page is the short version.
|
|
|
8
8
|
|
|
9
9
|
## Development Setup
|
|
10
10
|
|
|
11
|
+
The project needs Python 3.11 or newer. `requires-python` is `>=3.11` since
|
|
12
|
+
0.4.0, so a `.venv` created with 3.10 refuses the editable install below:
|
|
13
|
+
recreate it with a newer interpreter.
|
|
14
|
+
|
|
11
15
|
```bash
|
|
12
16
|
python -m venv .venv
|
|
13
17
|
.venv\Scripts\activate
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: easyvista-python-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Typed Python client for the EasyVista Service Manager REST API
|
|
5
5
|
Project-URL: Homepage, https://github.com/baraline/easyvista_python_client
|
|
6
6
|
Project-URL: Documentation, https://easyvista-python-client.readthedocs.io/en/latest/
|
|
@@ -14,7 +14,6 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -22,13 +21,25 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
22
21
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
23
|
Classifier: Typing :: Typed
|
|
25
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.11
|
|
26
25
|
Requires-Dist: httpx>=0.27
|
|
27
26
|
Requires-Dist: pydantic>=2.8
|
|
28
27
|
Requires-Dist: tenacity>=8.2
|
|
29
|
-
|
|
28
|
+
Provides-Extra: content
|
|
29
|
+
Requires-Dist: beautifulsoup4>=4.15; extra == 'content'
|
|
30
|
+
Requires-Dist: cmarkgfm>=2025.10.22; extra == 'content'
|
|
31
|
+
Requires-Dist: markdown-it-py<4,>=3.0; extra == 'content'
|
|
32
|
+
Requires-Dist: markdownify<1.3,>=1.2.3; extra == 'content'
|
|
33
|
+
Requires-Dist: mdformat-tables<1.1,>=1.0; extra == 'content'
|
|
34
|
+
Requires-Dist: mdformat<0.8,>=0.7.22; extra == 'content'
|
|
30
35
|
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: beautifulsoup4>=4.15; extra == 'dev'
|
|
31
37
|
Requires-Dist: build>=1.2; extra == 'dev'
|
|
38
|
+
Requires-Dist: cmarkgfm>=2025.10.22; extra == 'dev'
|
|
39
|
+
Requires-Dist: markdown-it-py<4,>=3.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: markdownify<1.3,>=1.2.3; extra == 'dev'
|
|
41
|
+
Requires-Dist: mdformat-tables<1.1,>=1.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: mdformat<0.8,>=0.7.22; extra == 'dev'
|
|
32
43
|
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
33
44
|
Requires-Dist: numpydoc>=1.8; extra == 'dev'
|
|
34
45
|
Requires-Dist: pre-commit>=4.0; extra == 'dev'
|
|
@@ -40,15 +51,19 @@ Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
|
40
51
|
Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'dev'
|
|
41
52
|
Requires-Dist: sphinx<8.2,>=7.2; extra == 'dev'
|
|
42
53
|
Requires-Dist: tokenize-rt==6.2.0; extra == 'dev'
|
|
43
|
-
Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'dev'
|
|
44
54
|
Requires-Dist: twine>=7.0; extra == 'dev'
|
|
45
55
|
Requires-Dist: unasync==0.6.0; extra == 'dev'
|
|
46
56
|
Requires-Dist: vulture>=2.11; extra == 'dev'
|
|
47
57
|
Provides-Extra: docs
|
|
58
|
+
Requires-Dist: beautifulsoup4>=4.15; extra == 'docs'
|
|
59
|
+
Requires-Dist: cmarkgfm>=2025.10.22; extra == 'docs'
|
|
60
|
+
Requires-Dist: markdown-it-py<4,>=3.0; extra == 'docs'
|
|
61
|
+
Requires-Dist: markdownify<1.3,>=1.2.3; extra == 'docs'
|
|
62
|
+
Requires-Dist: mdformat-tables<1.1,>=1.0; extra == 'docs'
|
|
63
|
+
Requires-Dist: mdformat<0.8,>=0.7.22; extra == 'docs'
|
|
48
64
|
Requires-Dist: numpydoc>=1.8; extra == 'docs'
|
|
49
65
|
Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'docs'
|
|
50
66
|
Requires-Dist: sphinx<8.2,>=7.2; extra == 'docs'
|
|
51
|
-
Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'docs'
|
|
52
67
|
Description-Content-Type: text/markdown
|
|
53
68
|
|
|
54
69
|
# easyvista-python-client
|
|
@@ -56,7 +71,7 @@ Description-Content-Type: text/markdown
|
|
|
56
71
|
[](https://github.com/baraline/easyvista_python_client/actions/workflows/ci.yml)
|
|
57
72
|
[](https://codecov.io/gh/baraline/easyvista_python_client)
|
|
58
73
|
[](https://github.com/baraline/easyvista_python_client/blob/main/LICENSE)
|
|
59
|
-
[](https://github.com/baraline/easyvista_python_client)
|
|
60
75
|
[](https://easyvista-python-client.readthedocs.io/en/latest/)
|
|
61
76
|
|
|
62
77
|
|
|
@@ -82,6 +97,17 @@ Build it locally with `pip install -e ".[docs]"` then
|
|
|
82
97
|
pip install easyvista-python-client
|
|
83
98
|
```
|
|
84
99
|
|
|
100
|
+
Python 3.11 or newer. 0.4.0 dropped 3.10; on 3.10, pip installs 0.3.0, which
|
|
101
|
+
has no `content` extra: the converter below needs 3.11 or newer.
|
|
102
|
+
|
|
103
|
+
To read and write memo text as Markdown, add the optional `content` extra, which
|
|
104
|
+
brings a Markdown <-> HTML converter, `easyvista_python_client.content`. Its
|
|
105
|
+
Markdown is CommonMark with GFM tables:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install "easyvista-python-client[content]"
|
|
109
|
+
```
|
|
110
|
+
|
|
85
111
|
## Usage (sync)
|
|
86
112
|
|
|
87
113
|
```python
|
|
@@ -89,6 +115,7 @@ from easyvista_python_client import (
|
|
|
89
115
|
EasyvistaClient,
|
|
90
116
|
EasyvistaConfig,
|
|
91
117
|
PostRequest,
|
|
118
|
+
WorkflowEffect,
|
|
92
119
|
ev_equals_filter,
|
|
93
120
|
)
|
|
94
121
|
|
|
@@ -122,12 +149,10 @@ with EasyvistaClient(config) as client:
|
|
|
122
149
|
for t in client.iter_tickets(search=open_status, page_size=100, max_records=1000):
|
|
123
150
|
... # async: `async for t in client.iter_tickets(...)`
|
|
124
151
|
|
|
125
|
-
# close
|
|
126
|
-
# optional -- `client.close_ticket(ticket.rfc_number)` sends the close with
|
|
127
|
-
# no status of its own, but where that lands the ticket is not established
|
|
128
|
-
# by this package; see the user guide before relying on it.
|
|
152
|
+
# close only when closing is the intent: it interrupts the ticket's workflow.
|
|
129
153
|
client.close_ticket(
|
|
130
154
|
ticket.rfc_number,
|
|
155
|
+
allow_workflow_effect=WorkflowEffect.INTERRUPTS,
|
|
131
156
|
status_guid="{00000000-0000-0000-0000-000000000000}",
|
|
132
157
|
delete_actions=1,
|
|
133
158
|
comment="Resolved",
|
|
@@ -185,7 +210,15 @@ client.end_action(
|
|
|
185
210
|
> action only ends it; ending the ticket's open workflow step advances the
|
|
186
211
|
> workflow and moves the ticket's status. Naming `action_id` is therefore
|
|
187
212
|
> required — the vendor's id-less "end every open action" form is behind an
|
|
188
|
-
> explicit `end_all=True`.
|
|
213
|
+
> explicit `end_all=True`. Ending a workflow step, or ending every open action
|
|
214
|
+
> with `end_all=True`, is refused unless the call passes
|
|
215
|
+
> `allow_workflow_effect=WorkflowEffect.ADVANCES`. Ending an action you
|
|
216
|
+
> created yourself needs no opt-in, with one unmeasured exception: whether an
|
|
217
|
+
> action `create_action` creates under the workflow step carries a
|
|
218
|
+
> `WORKFLOW_ID` has not been measured, and if it does, `end_action` refuses to
|
|
219
|
+
> end it without `ADVANCES` (the safe direction).
|
|
220
|
+
> The vendor documents no status setter, and this package has none: a ticket's
|
|
221
|
+
> status follows its workflow (user guide, "Changing a ticket's status").
|
|
189
222
|
|
|
190
223
|
## Assets and documents
|
|
191
224
|
|