File: test_manifest.py

package info (click to toggle)
python-npe2 0.7.8-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 828 kB
  • sloc: python: 7,088; makefile: 19
file content (210 lines) | stat: -rw-r--r-- 7,231 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
from importlib import metadata
from pathlib import Path
from unittest.mock import patch

import pytest

from npe2 import PluginManifest
from npe2._pydantic_compat import ValidationError
from npe2.manifest import PackageMetadata
from npe2.manifest.schema import ENTRY_POINT

SAMPLE_PLUGIN_NAME = "my-plugin"
SAMPLE_MODULE_NAME = "my_plugin"


def test_sample_plugin_valid(sample_manifest):
    assert sample_manifest


def test_discover_empty():
    # sanity check to make sure sample_plugin must be in path to be discovered
    results = PluginManifest.discover()
    manifests = [result.manifest.name for result in results if result.manifest]
    assert SAMPLE_PLUGIN_NAME not in manifests


def test_schema():
    assert isinstance(PluginManifest.schema_json(), str)

    dschema = PluginManifest.schema()
    assert isinstance(dschema, dict)
    assert "name" in dschema["properties"]


def test_discover(uses_sample_plugin):
    discover_results = list(PluginManifest.discover())
    assert len(discover_results) == 1
    [(manifest, distribution, error)] = discover_results
    assert manifest and manifest.name == SAMPLE_PLUGIN_NAME
    assert distribution
    entrypoint = next(iter(distribution.entry_points))
    assert entrypoint and entrypoint.group == "napari.manifest" == ENTRY_POINT
    assert entrypoint.value == f"{SAMPLE_MODULE_NAME}:napari.yaml"
    assert error is None


@pytest.mark.filterwarnings("ignore:Implicit None on return values is deprecated")
def test_discover_errors(tmp_path: Path):
    """testing various discovery errors"""
    # package with proper `napari.manifest` entry_point, but invalid pointer to
    # a manifest should yield an error in results
    a = tmp_path / "a"
    a.mkdir()
    a_ep = a / "entry_points.txt"
    bad_value = "asdfsad:blahblahblah.yaml"
    a_ep.write_text(f"[napari.manifest]\n{SAMPLE_PLUGIN_NAME} = {bad_value}")

    # package with proper `napari.manifest` entry_point, but invalid manifest
    b = tmp_path / "b"
    b.mkdir()
    b_ep = b / "entry_points.txt"
    b_ep.write_text("[napari.manifest]\nsome_plugin = module:napari.yaml")
    module = tmp_path / "module"
    module.mkdir()
    (module / "napari.yaml").write_text("name: hi??")

    # a regular package, with out napari.manifest entry_point should just be skipped
    c = tmp_path / "c"
    c.mkdir()
    c_ep = c / "entry_points.txt"
    c_ep.write_text("[console.scripts]\nsomething = something")

    dists = [
        metadata.PathDistribution(a),
        metadata.PathDistribution(b),
        metadata.PathDistribution(c),
    ]

    with patch.object(metadata, "distributions", return_value=dists):
        discover_results = list(PluginManifest.discover(paths=[tmp_path]))

    assert len(discover_results) == 2
    res_a, res_b = discover_results
    assert res_a.manifest is None
    assert res_a.distribution
    assert next(iter(res_a.distribution.entry_points)).value == bad_value
    assert "Cannot find module 'asdfsad'" in str(res_a.error)

    assert res_b.manifest is None
    assert res_b.distribution
    assert next(iter(res_b.distribution.entry_points)).value == "module:napari.yaml"
    assert isinstance(res_b.error, ValidationError)


def test_package_meta(uses_sample_plugin):
    direct_meta = PackageMetadata.for_package(SAMPLE_PLUGIN_NAME)
    assert direct_meta.name == SAMPLE_PLUGIN_NAME
    assert direct_meta.version == "1.2.3"
    discover_results = list(PluginManifest.discover())
    [(manifest, *_)] = discover_results
    assert manifest
    assert manifest.package_metadata == direct_meta

    assert manifest.author == direct_meta.author == "The Black Knight"
    assert manifest.description == direct_meta.summary == "My napari plugin"
    assert manifest.package_version == direct_meta.version == "1.2.3"
    assert manifest.license == direct_meta.license == "BSD-3"


def test_all_package_meta():
    """make sure PackageMetadata works for whatever packages are in the environment.

    just a brute force way to get a little more validation coverage
    """
    for d in metadata.distributions():
        assert PackageMetadata.from_dist_metadata(d.metadata)


@pytest.mark.parametrize("format", ["toml", "json", "yaml", "pyproject"])
def test_export_round_trip(sample_manifest, tmp_path, format):
    """Test that an exported manifest can be round-tripped."""
    if format == "pyproject":
        out_file = tmp_path / "pyproject.toml"
        out_file.write_text(sample_manifest.toml(pyproject=True))
    else:
        out_file = tmp_path / f"napari.{format}"
        out_file.write_text(getattr(sample_manifest, format)())
    assert sample_manifest == PluginManifest.from_file(out_file)


def test_from_distribution(uses_sample_plugin):
    mf = PluginManifest.from_distribution(SAMPLE_PLUGIN_NAME)
    assert mf.name == SAMPLE_PLUGIN_NAME
    assert mf.package_metadata == PackageMetadata.for_package(SAMPLE_PLUGIN_NAME)

    with pytest.raises(metadata.PackageNotFoundError):
        _ = PluginManifest.from_distribution("not-an-installed-package")

    with pytest.raises(ValueError) as e:
        # valid package, but doesn't have a manifest
        _ = PluginManifest.from_distribution("pytest")
    assert "exists but does not provide a napari manifest" in str(e.value)


def test_from_package_name_err():
    with pytest.raises(ValueError) as e:
        PluginManifest._from_package_or_name("nonsense")
    assert "Could not find manifest for 'nonsense'" in str(e.value)


def test_dotted_name_with_command():
    with pytest.raises(ValidationError, match="must start with the current package"):
        PluginManifest(
            name="plugin.plugin-sample",
            contributions={"commands": [{"id": "plugin.command", "title": "Sample"}]},
        )
    with pytest.raises(ValidationError, match="must begin with the package name"):
        PluginManifest(
            name="plugin.plugin-sample",
            contributions={
                "commands": [{"id": "plugin.plugin-samplecommand", "title": "Sample"}]
            },
        )

    PluginManifest(
        name="plugin.plugin-sample",
        contributions={
            "commands": [{"id": "plugin.plugin-sample.command", "title": "Sample"}]
        },
    )


def test_visibility():
    mf = PluginManifest(name="myplugin")
    assert mf.is_visible

    mf = PluginManifest(name="myplugin", visibility="hidden")
    assert not mf.is_visible

    with pytest.raises(ValidationError):
        mf = PluginManifest(name="myplugin", visibility="other")


def test_icon():
    PluginManifest(name="myplugin", icon="my_plugin:myicon.png")


def test_dotted_plugin_name():
    """Test that"""
    name = "some.namespaced.plugin"
    cmd_id = f"{name}.frame_rate_widget"
    mf = PluginManifest(
        name=name,
        contributions={
            "commands": [
                {
                    "id": cmd_id,
                    "title": "open my widget",
                }
            ],
            "widgets": [
                {
                    "command": cmd_id,
                    "display_name": "Plot frame rate",
                }
            ],
        },
    )
    assert mf.contributions.widgets
    assert mf.contributions.widgets[0].plugin_name == name