easyvista-python-client 0.4.2__tar.gz → 0.4.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.
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/CHANGELOG.md +31 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/PKG-INFO +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/content.rst +54 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_version.py +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/content/__init__.py +6 -2
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/content/conversion.py +235 -21
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/pyproject.toml +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-asset-workflow/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-client-setup/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-directory/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-document-workflow/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-instance-discovery/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-reporting-and-context/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-search-syntax/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-ticket-actions/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-ticket-workflow/SKILL.md +1 -1
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/.gitignore +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/.pre-commit-config.yaml +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/.readthedocs.yaml +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/CONTRIBUTING.md +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/LICENSE +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/README.md +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/_static/.gitkeep +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/_templates/.gitkeep +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/api_reference.rst +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/conf.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/development.rst +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/index.rst +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/installation.rst +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/publishing.rst +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/sponsoring.rst +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/user_guide.rst +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/vendor-api-reference.md +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/__init__.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_async/__init__.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_async/_concurrency.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_async/_transport.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_async/client.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_fields.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_html.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_sync/__init__.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_sync/_concurrency.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_sync/_transport.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_sync/client.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_transport.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/config.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/context.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/directory.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/discovery.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/exceptions.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/field_model.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/filters.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/__init__.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/action.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/asset.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/common.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/department.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/document.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/employee.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/generic.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/models/request.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/pagination.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/py.typed +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/references.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/reporting.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/__init__.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/actions.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/assets.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/departments.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/descriptor.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/discovery.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/documents.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/employees.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/resources/requests.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/timestamps.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/workflow.py +0 -0
- {easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/README.md +0 -0
|
@@ -15,6 +15,35 @@ is the error. Tags carry no `v` prefix.
|
|
|
15
15
|
|
|
16
16
|
## [Unreleased]
|
|
17
17
|
|
|
18
|
+
## [0.4.3] - 2026-10-08
|
|
19
|
+
|
|
20
|
+
A patch release of the content reader: a caller can recognise the images a
|
|
21
|
+
memo embeds and decide, link by link, what a read writes. Without the new
|
|
22
|
+
argument a read is unchanged, byte for byte; writing is unchanged, and so is
|
|
23
|
+
everything outside `easyvista_python_client.content`.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`from_transport(..., rewrite_link=callback)`.** The callback is called with
|
|
28
|
+
a `Link` for each link and image the read meets outside code -- an image
|
|
29
|
+
before the link around it, with that link's `enclosing_href` -- and its
|
|
30
|
+
answer is written: `None` keeps the reader's output, a `str` is written as
|
|
31
|
+
literal text, a `Link` replaces the target (an empty `href` drops a link and
|
|
32
|
+
keeps its content, or writes an image as its text). The callback is held in
|
|
33
|
+
a context variable, so concurrent reads each use their own. What it raises
|
|
34
|
+
reaches the caller unchanged; a `ValueError` is not taken for markdownify's
|
|
35
|
+
and answered with the text fallback.
|
|
36
|
+
- **`Link` and `RewriteLink`**, exported from `easyvista_python_client.content`.
|
|
37
|
+
- **`EasyvistaContentConverter.document_id_of(src)`** returns the
|
|
38
|
+
`DOCUMENT_ID` of an embedded image's `src`
|
|
39
|
+
(`@@EMBEDDED_IMAGE_PATH@@<DOCUMENT_ID>`, how EasyVista's editor embeds a
|
|
40
|
+
pasted image), and **`document_image(document_id, alt=...)`** the Markdown
|
|
41
|
+
of one, spelled as the reader spells it. A `Link` for such an image carries
|
|
42
|
+
its `document_id`. Measured on one instance on 2026-10-07: an image written
|
|
43
|
+
that way through the API displays, for an attachment pasted in the editor
|
|
44
|
+
and for one uploaded with `add_document`.
|
|
45
|
+
- `EasyvistaContentConverter.EMBEDDED_IMAGE_PREFIX`.
|
|
46
|
+
|
|
18
47
|
## [0.4.2] - 2026-10-07
|
|
19
48
|
|
|
20
49
|
A patch release of the content writer: a link written into a memo opens in a
|
|
@@ -1661,7 +1690,8 @@ Initial public release.
|
|
|
1661
1690
|
status/error code, with non-retryable validation errors (HTTP 590, code 2013).
|
|
1662
1691
|
- `py.typed` marker — the package ships inline type information.
|
|
1663
1692
|
|
|
1664
|
-
[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.
|
|
1693
|
+
[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.3...HEAD
|
|
1694
|
+
[0.4.3]: https://github.com/baraline/easyvista_python_client/compare/0.4.2...0.4.3
|
|
1665
1695
|
[0.4.2]: https://github.com/baraline/easyvista_python_client/compare/0.4.1...0.4.2
|
|
1666
1696
|
[0.4.1]: https://github.com/baraline/easyvista_python_client/compare/0.4.0...0.4.1
|
|
1667
1697
|
[0.4.0]: https://github.com/baraline/easyvista_python_client/compare/0.3.0...0.4.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: easyvista-python-client
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
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/
|
|
@@ -234,6 +234,60 @@ reported as
|
|
|
234
234
|
:class:`~easyvista_python_client.exceptions.EasyvistaContentError`. A block
|
|
235
235
|
quote nested 100,000 levels deep rendered without one (measured 2026-10-02).
|
|
236
236
|
|
|
237
|
+
Embedded images, and rewriting links as they are read
|
|
238
|
+
-----------------------------------------------------
|
|
239
|
+
|
|
240
|
+
EasyVista's editor keeps an image pasted into a memo as an attachment of the
|
|
241
|
+
request, named like ``image_emb_001.png``, and embeds it as
|
|
242
|
+
``<img src="@@EMBEDDED_IMAGE_PATH@@<DOCUMENT_ID>" />``. Without a callback the
|
|
243
|
+
reader writes that as any other image, ````,
|
|
244
|
+
which displays nowhere but in EasyVista. Three helpers let a caller do better.
|
|
245
|
+
|
|
246
|
+
``EasyvistaContentConverter.document_id_of(src)`` returns the ``DOCUMENT_ID``
|
|
247
|
+
an image's ``src`` embeds, or ``None``. ``document_image(document_id, alt=...)``
|
|
248
|
+
returns the Markdown of such an image, spelled exactly as the reader spells
|
|
249
|
+
it, so ``to_transport`` writes the editor's form and ``from_transport`` reads
|
|
250
|
+
it back unchanged. **Tier 4** -- measured on one instance on 2026-10-07: an
|
|
251
|
+
image written that way through the API displays, both for an attachment
|
|
252
|
+
pasted in the editor and for one uploaded with ``add_document``. The memo
|
|
253
|
+
displays it only while the attachment is on the same request.
|
|
254
|
+
|
|
255
|
+
``from_transport(..., rewrite_link=callback)`` calls ``callback`` with a
|
|
256
|
+
:class:`~easyvista_python_client.content.Link` for each link and each image it
|
|
257
|
+
meets outside code, an image before the link around it. A ``Link`` carries the
|
|
258
|
+
``href`` (an image's ``src``), the ``text`` (an image's ``alt``), the
|
|
259
|
+
``title``, whether it is an ``image``, an embedded image's ``document_id``, and
|
|
260
|
+
for an image inside a link that link's ``enclosing_href``. What the callback
|
|
261
|
+
answers is written:
|
|
262
|
+
|
|
263
|
+
* ``None``: what the reader writes without a callback;
|
|
264
|
+
* a ``str``: that text, literally -- escaped as any text the memo displays, so
|
|
265
|
+
it cannot become markup;
|
|
266
|
+
* a ``Link``: a link's ``href`` and ``title`` around the link's content as
|
|
267
|
+
read, or an image's ``href`` as its ``src``, ``text`` as its ``alt`` and
|
|
268
|
+
``title``;
|
|
269
|
+
* a ``Link`` with an empty ``href``: a link is dropped and its content kept, an
|
|
270
|
+
image is written as its ``text``.
|
|
271
|
+
|
|
272
|
+
.. code-block:: python
|
|
273
|
+
|
|
274
|
+
from easyvista_python_client.content import EasyvistaContentConverter, Link
|
|
275
|
+
|
|
276
|
+
def name_attachments(link: Link) -> Link | str | None:
|
|
277
|
+
if link.document_id is not None:
|
|
278
|
+
return f"[attachment {link.document_id}]"
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
EasyvistaContentConverter.from_transport(memo, rewrite_link=name_attachments)
|
|
282
|
+
|
|
283
|
+
The callback is held for the duration of one read, per thread and per task,
|
|
284
|
+
so concurrent reads each use their own, and a read inside a callback has its
|
|
285
|
+
own too. What the callback raises reaches the caller unchanged -- a
|
|
286
|
+
``ValueError`` included, which the reader would otherwise take for one of
|
|
287
|
+
markdownify's and answer by reading the body as its text. An answer of any
|
|
288
|
+
other type raises ``TypeError``. It is not called for a value passed through
|
|
289
|
+
as Markdown (``plain_text_is_markdown=True``), nor for a body read as its text.
|
|
290
|
+
|
|
237
291
|
It is not a sanitiser
|
|
238
292
|
---------------------
|
|
239
293
|
|
|
@@ -13,6 +13,10 @@ stack left to report it otherwise (see ``docs/content.rst``).
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
from easyvista_python_client.content.conversion import
|
|
16
|
+
from easyvista_python_client.content.conversion import (
|
|
17
|
+
EasyvistaContentConverter,
|
|
18
|
+
Link,
|
|
19
|
+
RewriteLink,
|
|
20
|
+
)
|
|
17
21
|
|
|
18
|
-
__all__ = ["EasyvistaContentConverter"]
|
|
22
|
+
__all__ = ["EasyvistaContentConverter", "Link", "RewriteLink"]
|
|
@@ -59,9 +59,11 @@ import re
|
|
|
59
59
|
import string
|
|
60
60
|
import unicodedata
|
|
61
61
|
from collections.abc import Callable, Iterator, Mapping, MutableMapping, Sequence
|
|
62
|
+
from contextvars import ContextVar
|
|
63
|
+
from dataclasses import dataclass
|
|
62
64
|
from functools import cached_property
|
|
63
65
|
from html import escape, unescape
|
|
64
|
-
from typing import Any, cast
|
|
66
|
+
from typing import Any, ClassVar, cast
|
|
65
67
|
|
|
66
68
|
from easyvista_python_client.exceptions import EasyvistaContentError
|
|
67
69
|
|
|
@@ -396,6 +398,97 @@ def _title(title: str) -> str:
|
|
|
396
398
|
return ' "' + re.sub(r'[\\"]', r"\\\g<0>", title) + '"' if title else ""
|
|
397
399
|
|
|
398
400
|
|
|
401
|
+
#: How a memo embeds an image: EasyVista's editor stores a pasted image as an
|
|
402
|
+
#: attachment of the request and writes ``<img src="@@EMBEDDED_IMAGE_PATH@@...">``
|
|
403
|
+
#: with that attachment's ``DOCUMENT_ID``. Measured 2026-10-07 on one instance
|
|
404
|
+
#: (tier 4): an image written that way through the API displays too, for an
|
|
405
|
+
#: attachment pasted in the editor and for one uploaded with ``add_document``.
|
|
406
|
+
EMBEDDED_IMAGE_PREFIX = "@@EMBEDDED_IMAGE_PATH@@"
|
|
407
|
+
|
|
408
|
+
#: A ``DOCUMENT_ID``, as an embedded image's ``src`` carries one. Measured: 40 hex
|
|
409
|
+
#: digits for an image pasted in the editor, an account number, ``_`` and 64 hex
|
|
410
|
+
#: digits for a file uploaded through the API. The class is wider than both.
|
|
411
|
+
_DOCUMENT_ID = re.compile(r"[A-Za-z0-9_.-]{1,128}")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@dataclass(frozen=True, slots=True)
|
|
415
|
+
class Link:
|
|
416
|
+
"""A link or an image the reader meets, as a ``rewrite_link`` callback sees it.
|
|
417
|
+
|
|
418
|
+
Attributes
|
|
419
|
+
----------
|
|
420
|
+
href : str
|
|
421
|
+
An ``<a>``'s ``href`` or an ``<img>``'s ``src``, character references
|
|
422
|
+
decoded.
|
|
423
|
+
text : str
|
|
424
|
+
An ``<a>``'s displayed text or an ``<img>``'s ``alt``, whitespace
|
|
425
|
+
collapsed.
|
|
426
|
+
title : str
|
|
427
|
+
The ``title``, or ``""``.
|
|
428
|
+
image : bool
|
|
429
|
+
``True`` for an ``<img>``.
|
|
430
|
+
document_id : str or None
|
|
431
|
+
For an ``<img>`` embedding an attachment of the request
|
|
432
|
+
(:data:`EMBEDDED_IMAGE_PREFIX` and a ``DOCUMENT_ID``), that
|
|
433
|
+
``DOCUMENT_ID``; ``None`` otherwise, and always for an ``<a>``.
|
|
434
|
+
enclosing_href : str or None
|
|
435
|
+
For an ``<img>`` inside a link, that link's ``href``; ``None``
|
|
436
|
+
otherwise. The image is read before the link around it.
|
|
437
|
+
"""
|
|
438
|
+
|
|
439
|
+
href: str
|
|
440
|
+
text: str = ""
|
|
441
|
+
title: str = ""
|
|
442
|
+
image: bool = False
|
|
443
|
+
document_id: str | None = None
|
|
444
|
+
enclosing_href: str | None = None
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
#: A ``rewrite_link`` callback: called with each link and image a read meets,
|
|
448
|
+
#: outside code. ``None`` keeps what the reader writes without one. A ``str`` is
|
|
449
|
+
#: written instead, as literal text. A :class:`Link` is written in the reader's
|
|
450
|
+
#: own spelling: a link's ``href`` and ``title``, around the link's content as
|
|
451
|
+
#: read; an image's ``href`` as its ``src``, ``text`` as its ``alt`` and
|
|
452
|
+
#: ``title``. An empty ``href`` drops a link and keeps its content, and writes an
|
|
453
|
+
#: image as its ``text``.
|
|
454
|
+
RewriteLink = Callable[[Link], "Link | str | None"]
|
|
455
|
+
|
|
456
|
+
_REWRITE: ContextVar[RewriteLink | None] = ContextVar("rewrite_link", default=None)
|
|
457
|
+
"""The callback of the read in progress. A context variable rather than state
|
|
458
|
+
on the shared converter: each thread and each task reads with its own."""
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class _CallbackRaised(Exception):
|
|
462
|
+
"""What a ``rewrite_link`` callback raised, carried past the reader's fallbacks.
|
|
463
|
+
|
|
464
|
+
The reader answers a ``ValueError`` by reading the body as its text; a
|
|
465
|
+
callback's own ``ValueError`` must not pass for markdownify's and quietly
|
|
466
|
+
cost the body its formatting.
|
|
467
|
+
"""
|
|
468
|
+
|
|
469
|
+
def __init__(self, error: Exception) -> None:
|
|
470
|
+
super().__init__(error)
|
|
471
|
+
self.error = error
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _rewritten(link: Link) -> Link | str | None:
|
|
475
|
+
"""Ask the read's callback about ``link``; ``None`` when there is none."""
|
|
476
|
+
|
|
477
|
+
rewrite = _REWRITE.get()
|
|
478
|
+
if rewrite is None:
|
|
479
|
+
return None
|
|
480
|
+
try:
|
|
481
|
+
answer: object = rewrite(link) # typed as what it is, not what it should be
|
|
482
|
+
except Exception as exc:
|
|
483
|
+
raise _CallbackRaised(exc) from exc
|
|
484
|
+
if answer is None or isinstance(answer, (Link, str)):
|
|
485
|
+
return answer
|
|
486
|
+
error = TypeError(
|
|
487
|
+
f"rewrite_link returned {type(answer).__name__}; expected Link, str or None"
|
|
488
|
+
)
|
|
489
|
+
raise _CallbackRaised(error)
|
|
490
|
+
|
|
491
|
+
|
|
399
492
|
class _Converter(MarkdownConverter):
|
|
400
493
|
"""``markdownify``, with what CommonMark and the reader's glue need on top.
|
|
401
494
|
|
|
@@ -553,15 +646,27 @@ class _Converter(MarkdownConverter):
|
|
|
553
646
|
body, _, tail = rest.rpartition("```")
|
|
554
647
|
return f"{head}{fence}{body}{fence}{tail}"
|
|
555
648
|
|
|
649
|
+
def _literal(self, text: str, parent_tags: set[str]) -> str:
|
|
650
|
+
"""``text`` escaped as the reader escapes a text node: displayed as it is."""
|
|
651
|
+
|
|
652
|
+
return self.escape(" ".join(text.split()), parent_tags)
|
|
653
|
+
|
|
556
654
|
def convert_a(self, el: Tag, text: str, parent_tags: set[str]) -> str:
|
|
557
655
|
"""A link; ``<url>`` when its text is its URL and reads back unchanged."""
|
|
558
656
|
|
|
657
|
+
if "_noformat" in parent_tags:
|
|
658
|
+
return text
|
|
559
659
|
href = str(el.get("href") or "")
|
|
660
|
+
title = str(el.get("title") or "")
|
|
661
|
+
answer = _rewritten(Link(href, " ".join(el.get_text().split()), title))
|
|
662
|
+
if isinstance(answer, str):
|
|
663
|
+
return self._literal(answer, parent_tags)
|
|
664
|
+
if answer is not None:
|
|
665
|
+
href, title = answer.href, answer.title
|
|
560
666
|
edges = _EDGES.fullmatch(text)
|
|
561
|
-
if
|
|
667
|
+
if not href or edges is None or not edges[2]:
|
|
562
668
|
return text
|
|
563
669
|
before, inner, after = edges.groups()
|
|
564
|
-
title = str(el.get("title") or "")
|
|
565
670
|
if not title and el.get_text() == href and _AUTOLINK.fullmatch(href):
|
|
566
671
|
return f"{before}<{href}>{after}"
|
|
567
672
|
return f"{before}[{inner}]({_destination(href)}{_title(title)}){after}"
|
|
@@ -569,12 +674,38 @@ class _Converter(MarkdownConverter):
|
|
|
569
674
|
def convert_img(self, el: Tag, text: str, parent_tags: set[str]) -> str:
|
|
570
675
|
"""An image, wherever it is: a cell or a heading holds one too."""
|
|
571
676
|
|
|
677
|
+
src = str(el.get("src") or "")
|
|
572
678
|
alt = " ".join(str(el.get("alt") or "").split())
|
|
679
|
+
title = str(el.get("title") or "")
|
|
680
|
+
if "_noformat" not in parent_tags:
|
|
681
|
+
embedded = EasyvistaContentConverter.document_id_of(src)
|
|
682
|
+
around = el.find_parent("a")
|
|
683
|
+
answer = _rewritten(
|
|
684
|
+
Link(
|
|
685
|
+
src,
|
|
686
|
+
alt,
|
|
687
|
+
title,
|
|
688
|
+
image=True,
|
|
689
|
+
document_id=embedded,
|
|
690
|
+
enclosing_href=(
|
|
691
|
+
None if around is None else str(around.get("href") or "")
|
|
692
|
+
),
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
if isinstance(answer, str):
|
|
696
|
+
return self._literal(answer, parent_tags)
|
|
697
|
+
if answer is not None and not answer.href:
|
|
698
|
+
return self._literal(answer.text, parent_tags)
|
|
699
|
+
if answer is not None:
|
|
700
|
+
src, alt, title = (
|
|
701
|
+
answer.href,
|
|
702
|
+
" ".join(answer.text.split()),
|
|
703
|
+
answer.title,
|
|
704
|
+
)
|
|
573
705
|
alt = str(self._inherited("escape")(alt, parent_tags))
|
|
574
706
|
if alt.startswith("^") and "_noformat" not in parent_tags:
|
|
575
707
|
alt = "\\" + alt # cmark-gfm reads " or ''))})"
|
|
708
|
+
return f"}{_title(title)})"
|
|
578
709
|
|
|
579
710
|
|
|
580
711
|
class _Node(RenderTreeNode):
|
|
@@ -784,11 +915,41 @@ def _text_of(html: str) -> str:
|
|
|
784
915
|
return "\n".join(line for line in lines if line)
|
|
785
916
|
|
|
786
917
|
|
|
918
|
+
def _converted(content: str) -> str:
|
|
919
|
+
"""Read memo HTML, falling back to its text where the conversion cannot."""
|
|
920
|
+
|
|
921
|
+
try:
|
|
922
|
+
try:
|
|
923
|
+
return html_to_markdown(content)
|
|
924
|
+
except (RecursionError, ValueError): # ValueError: markdownify's
|
|
925
|
+
# int() of a colspan or start such as "²" or 5,000 digits
|
|
926
|
+
return html_to_markdown(_plain_text_html(_text_of(content)))
|
|
927
|
+
except ParserRejectedMarkup:
|
|
928
|
+
# html.parser gives up on a few malformed declarations; the
|
|
929
|
+
# body's words are still worth more than an exception.
|
|
930
|
+
text = unescape(_ANY_TAG.sub(" ", content))
|
|
931
|
+
return html_to_markdown(_plain_text_html(" ".join(text.split())))
|
|
932
|
+
except _CallbackRaised:
|
|
933
|
+
raise
|
|
934
|
+
except Exception as exc:
|
|
935
|
+
raise EasyvistaContentError(
|
|
936
|
+
"Could not convert EasyVista memo HTML to Markdown "
|
|
937
|
+
f"({type(exc).__name__}: {exc})."
|
|
938
|
+
) from exc
|
|
939
|
+
|
|
940
|
+
|
|
787
941
|
class EasyvistaContentConverter:
|
|
788
942
|
"""Convert content between EasyVista memo HTML and canonical Markdown."""
|
|
789
943
|
|
|
944
|
+
EMBEDDED_IMAGE_PREFIX: ClassVar[str] = EMBEDDED_IMAGE_PREFIX
|
|
945
|
+
|
|
790
946
|
@staticmethod
|
|
791
|
-
def from_transport(
|
|
947
|
+
def from_transport(
|
|
948
|
+
value: object,
|
|
949
|
+
*,
|
|
950
|
+
plain_text_is_markdown: bool = False,
|
|
951
|
+
rewrite_link: RewriteLink | None = None,
|
|
952
|
+
) -> str:
|
|
792
953
|
"""Convert one EasyVista memo value into Markdown.
|
|
793
954
|
|
|
794
955
|
Parameters
|
|
@@ -806,6 +967,12 @@ class EasyvistaContentConverter:
|
|
|
806
967
|
``<kbd>`` is still Markdown, but Markdown that opens with an
|
|
807
968
|
autolink or other angle-bracketed text and carries inline HTML
|
|
808
969
|
further on is read as HTML, and loses that autolink.
|
|
970
|
+
rewrite_link : callable, optional
|
|
971
|
+
Called with a :class:`Link` for each link and image the read
|
|
972
|
+
meets outside code, an image before the link around it; its
|
|
973
|
+
answer decides what is written (:data:`RewriteLink`). Not called
|
|
974
|
+
for a value passed through as Markdown, nor for a body read as
|
|
975
|
+
its text. Without one, the read is what it always was.
|
|
809
976
|
|
|
810
977
|
Returns
|
|
811
978
|
-------
|
|
@@ -825,6 +992,10 @@ class EasyvistaContentConverter:
|
|
|
825
992
|
limit, where no stack is left even to report the failure as
|
|
826
993
|
``EasyvistaContentError`` (``docs/content.rst`` gives the
|
|
827
994
|
measured depths).
|
|
995
|
+
Exception
|
|
996
|
+
Whatever ``rewrite_link`` raised, unchanged; ``TypeError`` when it
|
|
997
|
+
answered something other than a :class:`Link`, a ``str`` or
|
|
998
|
+
``None``.
|
|
828
999
|
"""
|
|
829
1000
|
|
|
830
1001
|
content = str(value or "").strip()
|
|
@@ -840,22 +1011,65 @@ class EasyvistaContentConverter:
|
|
|
840
1011
|
# 3.11.14/3.12.12/3.13.6 rescans to the end for each (quadratic).
|
|
841
1012
|
head, end, tail = content.rpartition(">")
|
|
842
1013
|
content = head + end + tail.replace("<", "<")
|
|
1014
|
+
token = _REWRITE.set(rewrite_link)
|
|
843
1015
|
try:
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1016
|
+
return _converted(content)
|
|
1017
|
+
except _CallbackRaised as raised:
|
|
1018
|
+
callback_error = raised.error
|
|
1019
|
+
finally:
|
|
1020
|
+
_REWRITE.reset(token)
|
|
1021
|
+
raise callback_error # outside the handler: the callback's, as it raised it
|
|
1022
|
+
|
|
1023
|
+
@staticmethod
|
|
1024
|
+
def document_id_of(src: str) -> str | None:
|
|
1025
|
+
"""Return the ``DOCUMENT_ID`` an embedded image's ``src`` names.
|
|
1026
|
+
|
|
1027
|
+
``None`` unless ``src`` is exactly :data:`EMBEDDED_IMAGE_PREFIX`
|
|
1028
|
+
followed by a ``DOCUMENT_ID`` -- how EasyVista's editor refers to an
|
|
1029
|
+
image pasted into a memo, which it keeps as an attachment of the
|
|
1030
|
+
request.
|
|
1031
|
+
"""
|
|
1032
|
+
|
|
1033
|
+
if not src.startswith(EMBEDDED_IMAGE_PREFIX):
|
|
1034
|
+
return None
|
|
1035
|
+
document_id = src[len(EMBEDDED_IMAGE_PREFIX) :]
|
|
1036
|
+
return document_id if _DOCUMENT_ID.fullmatch(document_id) else None
|
|
1037
|
+
|
|
1038
|
+
@staticmethod
|
|
1039
|
+
def document_image(document_id: str, *, alt: str = "") -> str:
|
|
1040
|
+
"""Return the Markdown of an image embedding one of the request's attachments.
|
|
1041
|
+
|
|
1042
|
+
:meth:`to_transport` renders it as EasyVista's editor writes a pasted
|
|
1043
|
+
image, ``<img src="@@EMBEDDED_IMAGE_PATH@@<document_id>" alt="..." />``,
|
|
1044
|
+
and :meth:`from_transport` reads that back as this same Markdown: it
|
|
1045
|
+
is spelled by the reader itself. The memo displays the image once the
|
|
1046
|
+
attachment is on the same request.
|
|
1047
|
+
|
|
1048
|
+
Parameters
|
|
1049
|
+
----------
|
|
1050
|
+
document_id : str
|
|
1051
|
+
The attachment's ``DOCUMENT_ID``, as ``list_documents`` gives it.
|
|
1052
|
+
alt : str, optional
|
|
1053
|
+
The image's alternative text.
|
|
1054
|
+
|
|
1055
|
+
Raises
|
|
1056
|
+
------
|
|
1057
|
+
ValueError
|
|
1058
|
+
``document_id`` is not one: empty, or holding a character no
|
|
1059
|
+
``DOCUMENT_ID`` has.
|
|
1060
|
+
"""
|
|
1061
|
+
|
|
1062
|
+
if not _DOCUMENT_ID.fullmatch(document_id):
|
|
1063
|
+
raise ValueError(f"not an EasyVista DOCUMENT_ID: {document_id!r}")
|
|
1064
|
+
native = (
|
|
1065
|
+
f'<p><img src="{EMBEDDED_IMAGE_PREFIX}{document_id}" '
|
|
1066
|
+
f'alt="{escape(" ".join(alt.split()))}" /></p>'
|
|
1067
|
+
)
|
|
1068
|
+
token = _REWRITE.set(None) # a callback in force for an enclosing read
|
|
1069
|
+
try:
|
|
1070
|
+
return html_to_markdown(native)
|
|
1071
|
+
finally:
|
|
1072
|
+
_REWRITE.reset(token)
|
|
859
1073
|
|
|
860
1074
|
@staticmethod
|
|
861
1075
|
def to_transport(value: object) -> str:
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the assets resource."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and valid EasyVista credentials."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
`easyvista_python_client` ships both clients over one surface:
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/skills/easyvista-directory/SKILL.md
RENAMED
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the departments and employees resources (writes are additionally profile-gated)."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the documents sub-resource."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API. Every call here is a GET; nothing is created, updated or deleted."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the actions sub-resource."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the requests resource."
|
|
6
6
|
metadata:
|
|
7
7
|
package: easyvista-python-client
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.3"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/docs/vendor-api-reference.md
RENAMED
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_fields.py
RENAMED
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/_html.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/config.py
RENAMED
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/context.py
RENAMED
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/directory.py
RENAMED
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/discovery.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/filters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/reporting.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.4.2 → easyvista_python_client-0.4.3}/easyvista_python_client/workflow.py
RENAMED
|
File without changes
|
|
File without changes
|