Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ is the error. Tags carry no `v` prefix.

## [Unreleased]

## [0.4.2] - 2026-10-07

A patch release of the content writer: a link written into a memo opens in a
new window. Reading is unchanged, and so is everything outside
`easyvista_python_client.content`.

### Changed

- **Every link `to_transport` writes carries `target="_blank"
rel="noopener noreferrer"`**, as a link written in EasyVista's own editor
does. Without it, EasyVista's memo view opens the link in place, inside its
own window, so a link to another application seemed to do nothing (measured
on a live memo, 2026-10-07: links with and without the attributes, side by
side). Reading ignores both attributes, so `from_transport(to_transport(m))`
is unchanged for every link.

## [0.4.1] - 2026-10-02

A patch release of the content reader. A `<u>`, `<mark>` or `<ins>` round a
Expand Down Expand Up @@ -1645,7 +1661,8 @@ Initial public release.
status/error code, with non-retryable validation errors (HTTP 590, code 2013).
- `py.typed` marker — the package ships inline type information.

[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.1...HEAD
[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.2...HEAD
[0.4.2]: https://github.com/baraline/easyvista_python_client/compare/0.4.1...0.4.2
[0.4.1]: https://github.com/baraline/easyvista_python_client/compare/0.4.0...0.4.1
[0.4.0]: https://github.com/baraline/easyvista_python_client/compare/0.3.0...0.4.0
[0.3.0]: https://github.com/baraline/easyvista_python_client/compare/0.2.0...0.3.0
Expand Down
5 changes: 5 additions & 0 deletions docs/content.rst
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ options, ``HARDBREAKS`` and ``UNSAFE``:
* a fence becomes ``<pre><code>``, with the info string as a
``class="language-..."``;
* a table becomes a ``<table>`` with a ``<thead>``;
* a link opens in a new window: each ``<a href>`` the renderer writes carries
``target="_blank" rel="noopener noreferrer"``, as a link written in
EasyVista's editor does. Without it the memo view opens the link in place,
inside EasyVista's window (measured 2026-10-07). Reading ignores both
attributes, so a round trip is unchanged;
* raw HTML, inline or as a block, is passed through as written;
* ``""``, or only whitespace, stays ``""`` rather than becoming ``<p></p>``.

Expand Down
2 changes: 1 addition & 1 deletion easyvista_python_client/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
live only in ``__init__``.
"""

__version__ = "0.4.1"
__version__ = "0.4.2"
16 changes: 15 additions & 1 deletion easyvista_python_client/content/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,12 +748,26 @@ def html_to_markdown(html: str) -> str:
return str(_FORMATTER.render(_CONVERTER.convert_soup(soup))).strip()


#: A link as cmark-gfm writes one: an ``href``, an optional ``title``, nothing else.
_RENDERED_LINK = re.compile(r'<a href="[^"]*"(?: title="[^"]*")?>')

#: What EasyVista's own editor writes on a link: without it the memo view opens the
#: link in place, inside EasyVista's window, rather than in a new one.
_NEW_WINDOW = ' target="_blank" rel="noopener noreferrer"'


def markdown_to_html(markdown: str) -> str:
"""Render Markdown as HTML: CommonMark with GFM tables, through cmark-gfm."""
"""Render Markdown as HTML: CommonMark with GFM tables, through cmark-gfm.

Every link opens in a new window, the way a link written in EasyVista's
editor does: ``target="_blank" rel="noopener noreferrer"`` is added to each
link cmark-gfm writes. Reading ignores both attributes.
"""

html: str = cmarkgfm.markdown_to_html_with_extensions(
markdown, options=_RENDER_OPTIONS, extensions=["table"]
)
html = _RENDERED_LINK.sub(lambda link: link.group(0)[:-1] + _NEW_WINDOW + ">", html)
return html.strip()


Expand Down
66 changes: 64 additions & 2 deletions easyvista_python_client/content/tests/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
read = EasyvistaContentConverter.from_transport
render = EasyvistaContentConverter.to_transport

#: What every rendered link carries: EasyVista's editor writes it on a link it makes.
NEW_WINDOW = ' target="_blank" rel="noopener noreferrer"'


def test_content_is_markdown_in_python_and_html_for_easyvista() -> None:
assert read("<p>The printer is <strong>offline</strong>.</p>") == (
Expand Down Expand Up @@ -233,12 +236,12 @@ def test_deeply_nested_markdown_renders() -> None:
),
pytest.param(
"[x](javascript:alert(1))",
'<p><a href="javascript:alert(1)">x</a></p>',
f'<p><a href="javascript:alert(1)"{NEW_WINDOW}>x</a></p>',
id="link-target",
),
pytest.param( # python-markdown, before 0.4.0, left this as raw markup
"<javascript:alert(1)>",
'<p><a href="javascript:alert(1)">javascript:alert(1)</a></p>',
f'<p><a href="javascript:alert(1)"{NEW_WINDOW}>javascript:alert(1)</a></p>',
id="autolink",
),
],
Expand All @@ -258,3 +261,62 @@ def test_markup_a_memo_displays_as_text_stays_text_both_ways() -> None:

assert markdown == "\\<script>"
assert render(markdown) == "<p>&lt;script&gt;</p>"


# A link opens in a new window. Measured on a live memo view (2026-10-07): a link
# without ``target="_blank"`` opens in place, inside EasyVista's own window, and
# EasyVista's editor writes ``target="_blank" rel="noopener noreferrer"`` on a link.


@pytest.mark.parametrize(
("markdown", "html"),
[
pytest.param(
"[voir](https://example.org/p)",
f'<p><a href="https://example.org/p"{NEW_WINDOW}>voir</a></p>',
id="link",
),
pytest.param(
'[voir](https://example.org/p "le titre")',
f'<p><a href="https://example.org/p" title="le titre"{NEW_WINDOW}>'
"voir</a></p>",
id="titled",
),
pytest.param(
"<https://example.org/p>",
f'<p><a href="https://example.org/p"{NEW_WINDOW}>https://example.org/p</a></p>',
id="autolink",
),
pytest.param(
"[![a](https://example.org/i.png)](https://example.org/p)",
f'<p><a href="https://example.org/p"{NEW_WINDOW}>'
'<img src="https://example.org/i.png" alt="a" /></a></p>',
id="image-link",
),
],
)
def test_every_link_written_opens_in_a_new_window(markdown: str, html: str) -> None:
assert render(markdown) == html


def test_a_link_shown_as_code_is_text_and_gains_nothing() -> None:
assert render('`<a href="https://example.org">x</a>`') == (
"<p><code>&lt;a href=&quot;https://example.org&quot;&gt;x&lt;/a&gt;</code></p>"
)


@pytest.mark.parametrize(
"markdown",
[
"[voir](https://example.org/p)",
'[voir](https://example.org/p "le titre")',
"<https://example.org/p>",
"[![a](https://example.org/i.png)](https://example.org/p)",
],
ids=["link", "titled", "autolink", "image-link"],
)
def test_a_link_written_to_open_in_a_new_window_reads_back_as_written(
markdown: str,
) -> None:
# Reading ignores ``target`` and ``rel``, so a round trip stays exact.
assert read(render(markdown)) == markdown
2 changes: 1 addition & 1 deletion easyvista_python_client/testing/test_public_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _run_python(code: str) -> subprocess.CompletedProcess[str]:


def test_package_imports_and_has_version():
assert easyvista_python_client.__version__ == "0.4.1"
assert easyvista_python_client.__version__ == "0.4.2"


def test_public_exports_available():
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ exclude = [

[project]
name = "easyvista-python-client"
version = "0.4.1"
version = "0.4.2"
description = "Typed Python client for the EasyVista Service Manager REST API"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-asset-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
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."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-client-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and valid EasyVista credentials."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

`easyvista_python_client` ships both clients over one surface:
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-directory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
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)."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-document-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
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."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-instance-discovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
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."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-reporting-and-context/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-search-syntax/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-ticket-actions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
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."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
Expand Down
2 changes: 1 addition & 1 deletion skills/easyvista-ticket-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license: MIT
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."
metadata:
package: easyvista-python-client
version: "0.4.1"
version: "0.4.2"
---

> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
Expand Down
Loading