File: test_validations.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 (243 lines) | stat: -rw-r--r-- 7,658 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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import json

import pytest

from npe2 import PluginManifest
from npe2._pydantic_compat import ValidationError
from npe2.manifest import _validators

# the docstrings here are used to assert the validation error that is printed.

SAMPLE_PLUGIN_NAME = "my-plugin"


def _mutator_invalid_package_name(data):
    """'invalid??' is not a valid python package name."""
    data["name"] = "invalid??"


def _mutator_invalid_package_name2(data):
    """'_invalid' is not a valid python package name."""
    data["name"] = "_invalid"


def _mutator_command_not_begin_with_package_name(data):
    """Commands identifiers must start with the current package name"""
    assert "contributions" in data
    c = data["contributions"]["commands"][0]["id"]
    data["contributions"]["commands"][0]["id"] = ".".join(
        ["not_packagename", *c.split(".")[1:]]
    )


def _mutator_python_name_no_colon(data):
    """'this.has.no.colon' is not a valid python_name."""
    assert "contributions" in data
    data["contributions"]["commands"][0]["python_name"] = "this.has.no.colon"


def _mutator_python_name_locals(data):
    """functions defined in local scopes are not yet supported"""
    assert "contributions" in data
    data["contributions"]["commands"][0]["python_name"] = "mod:func.<locals>.another"


def _mutator_python_name_starts_with_number(data):
    """'1starts_with_number' is not a valid python_name."""
    assert "contributions" in data
    data["contributions"]["commands"][0]["python_name"] = "1starts_with_number"


def _mutator_writer_requires_non_empty_layer_types(data):
    """layer_types must not be empty"""
    data["contributions"]["writers"][0]["layer_types"] = []


def _mutator_writer_invalid_layer_type_constraint(data):
    """'image{' is not a valid LayerType"""
    data["contributions"]["writers"][0]["layer_types"].append("image{")


def _mutator_writer_invalid_file_extension_1(data):
    """Invalid file extension: Must have one character past the '.'"""
    data["contributions"]["writers"][0]["filename_extensions"] = ["*"]


def _mutator_writer_invalid_file_extension_2(data):
    """Invalid file extension: Must have one character past the '.'"""
    data["contributions"]["writers"][0]["filename_extensions"] = ["."]


def _mutator_invalid_icon(data):
    """is not a valid icon URL. It must start with 'https://'"""
    data["icon"] = "http://example.com/icon.png"


@pytest.mark.parametrize(
    "mutator",
    [
        _mutator_invalid_package_name,
        _mutator_invalid_package_name2,
        _mutator_command_not_begin_with_package_name,
        _mutator_python_name_no_colon,
        _mutator_python_name_locals,
        _mutator_python_name_starts_with_number,
        _mutator_writer_requires_non_empty_layer_types,
        _mutator_writer_invalid_layer_type_constraint,
        _mutator_writer_invalid_file_extension_1,
        _mutator_writer_invalid_file_extension_2,
        _mutator_invalid_icon,
    ],
)
def test_invalid(mutator, uses_sample_plugin):
    result = next(
        result
        for result in PluginManifest.discover()
        if result.manifest and result.manifest.name == SAMPLE_PLUGIN_NAME
    )
    assert result.error is None
    assert result.manifest is not None
    pm = result.manifest
    data = json.loads(pm.json(exclude_unset=True))
    mutator(data)
    with pytest.raises(ValidationError) as excinfo:
        PluginManifest(**data)
    assert mutator.__doc__ in str(excinfo.value)


def test_schema_version_too_high():
    with pytest.warns(
        UserWarning, match=r"\(999.999.999\) is newer than npe2's schema version"
    ):
        PluginManifest(name="sample", schema_version="999.999.999")


def test_invalid_python_name(uses_sample_plugin):
    mf = next(
        result
        for result in PluginManifest.discover()
        if result.manifest and result.manifest.name == SAMPLE_PLUGIN_NAME
    ).manifest
    assert mf and mf.contributions and mf.contributions.commands
    assert mf.contributions.commands[-1].python_name

    mf.validate_imports()
    mf.contributions.commands[-1].python_name += "_whoops"  # type: ignore
    with pytest.raises(ValidationError) as e:
        mf.validate_imports()
    assert "has no attribute 'make_widget_from_function_whoops'" in str(e.value)


def _valid_mutator_no_contributions(data):
    """
    Contributions can be absent, in which case the Pydantic model will set the
    default value to an empty Contributions model
    """
    del data["contributions"]


def _valid_mutator_no_contributions_empty(data):
    """
    Contributions can be an empty list, in which case the Pydantic model will set the
    default value to an empty Contributions model
    """
    data["contributions"] = []


def _valid_mutator_no_contributions_None(data):
    """
    Contributions can be None, in which case the Pydantic model will set the
    default value to None, and not the empty list, make sure that works.
    """
    # This is no longer recommended.  A missing contributions is fine, and an empty
    # list is fine.  We preserve this for backwards compatibility,
    # but providing None explicitly shouldn't be used
    data["contributions"] = None


@pytest.mark.parametrize(
    "mutator",
    [_valid_mutator_no_contributions, _valid_mutator_no_contributions_None],
)
def test_valid_mutations(mutator, uses_sample_plugin):
    assert mutator.__name__.startswith("_valid")

    pm = next(iter(PluginManifest.discover()))
    assert pm.manifest
    # make sure the data is a copy as we'll mutate it
    data = json.loads(pm.manifest.json(exclude_unset=True))
    mutator(data)
    PluginManifest(**data)


@pytest.mark.parametrize(
    "display_name",
    [
        "Here there everywhere and more with giggles and friends on top of a mountainside drinking tea",  # noqa
        "ab",
        "a   ",
        " abc",
        "_abc",
        "abc♱",
    ],
)
def test_invalid_display_names(display_name, uses_sample_plugin):
    field = PluginManifest.__fields__["display_name"]
    value, err = field.validate(display_name, {}, loc="display_name")
    assert err is not None


@pytest.mark.parametrize(
    "display_name",
    [
        "Some Cell & Stru买cture Segmenter",
        "Segment Blobs and Things with Membranes",
        "Segment: and Things.2 with Membranes ~= 8",
        "abc",
        "abc䜁䜂",
    ],
)
def test_valid_display_names(display_name, uses_sample_plugin):
    field = PluginManifest.__fields__["display_name"]
    value, err = field.validate(display_name, {}, loc="display_name")
    assert err is None


def test_display_name_default_is_valid():
    PluginManifest(name="")


@pytest.mark.parametrize(
    "expr",
    [
        "vectors{",
        "image",  # should parse fine, but be a duplication error
        "vectors{8,3}",
        "vectors{-1}",
        "vectors??",
        "other?",
    ],
)
def test_writer_invalid_layer_type_expressions(expr, uses_sample_plugin):
    result = next(
        result
        for result in PluginManifest.discover()
        if result.manifest and result.manifest.name == SAMPLE_PLUGIN_NAME
    )
    assert result.error is None
    assert result.manifest is not None
    pm = result.manifest
    data = json.loads(pm.json(exclude_unset=True))

    assert "contributions" in data
    assert "writers" in data["contributions"]
    data["contributions"]["writers"][0]["layer_types"].append(expr)

    with pytest.raises(ValidationError):
        PluginManifest(**data)


@pytest.mark.parametrize("id", ["badchar!?", "-bad-start", "has space"])
def test_invalid_command_id(id):
    with pytest.raises(ValueError):
        _validators.command_id(id)