Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup manifest #38

Merged
merged 13 commits into from
Nov 30, 2021
Merged
2 changes: 0 additions & 2 deletions EXAMPLE/main.py

This file was deleted.

29 changes: 0 additions & 29 deletions EXAMPLE/napari.json

This file was deleted.

26 changes: 0 additions & 26 deletions EXAMPLE/napari.yaml

This file was deleted.

20 changes: 0 additions & 20 deletions EXAMPLE/pyproject.toml

This file was deleted.

2 changes: 1 addition & 1 deletion npe2/_command_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def register(self, id: str, command: Callable) -> PDisposable:
if not callable(command):
raise TypeError(f"Cannot register non-callable command: {command}")

# TODO: validate argumemnts and type constraints
# TODO: validate arguments and type constraints
# possibly wrap command in a type validator?

self._commands[id] = command
Expand Down
18 changes: 12 additions & 6 deletions npe2/_from_npe1.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,19 @@ def manifest_from_npe1(
plugin_name = cast(str, plugin_name)

if not plugin_manager.is_registered(plugin_name):
# TODO: it would be nice to add some logic to prevent confusion here.
# for example... if the plugin name doesn't equal the package name, we
# should still be able to find it if the user gives a package name
# "plugin name" is not necessarily the package name. If the user
# supplies the package name, try to look it up and see if it's a plugin

try:
dist = distribution(plugin_name) # returns a list. multiple plugins?
plugin_name = dist.entry_points[0].name
dist = distribution(plugin_name)
plugin_name = next(
e.name for e in dist.entry_points if e.group == "napari.plugin"
)
except StopIteration:
raise PackageNotFoundError(
f"Could not find plugin {plugin_name!r}. Found a package by "
"that name but it lacked the 'napari.plugin' entry point group"
)
except PackageNotFoundError:
raise PackageNotFoundError(
f"Could not find plugin {plugin_name!r}\n"
Expand All @@ -102,7 +108,7 @@ def manifest_from_npe1(

return PluginManifest(
name=package,
publisher=standard_meta.get("author"),
author=standard_meta.get("author"),
description=standard_meta.get("summary"),
version=standard_meta.get("version"),
contributions=dict(parser.contributions),
Expand Down
8 changes: 5 additions & 3 deletions npe2/_plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __getitem__(self, index: Union[int, slice]) -> Set[Interval[T]]:
...


PluginName = str # this is defined on PluginManifest as `publisher.name`
PluginName = str # this is `PluginManifest.name`


class _ContributionsIndex:
Expand Down Expand Up @@ -318,8 +318,10 @@ def get_writer(
elif not ext and len(layer_types) == 1: # No extension, single layer.
ext = next(iter(writer.filename_extensions), "")
return writer, path + ext
else:
raise ValueError
# When the list of extensions for the writer doesn't match the
# extension in the filename, keep searching.

# Nothing got found
Comment on lines +321 to +324
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this fixes some broken tests I was getting on the napari side. napari's CI misses these at the moment because npe2 isn't installed when the test suite is running.

return None, path


Expand Down
74 changes: 19 additions & 55 deletions npe2/manifest/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import sys
import types
from contextlib import contextmanager
from enum import Enum
from importlib import import_module, util
from logging import getLogger
from pathlib import Path
Expand All @@ -14,7 +13,6 @@
Any,
Callable,
Iterator,
List,
NamedTuple,
Optional,
Sequence,
Expand All @@ -37,9 +35,6 @@
from importlib.metadata import EntryPoint


spdx_ids = (Path(__file__).parent / "spdx.txt").read_text().splitlines()
SPDX = Enum("SPDX", {i.replace("-", "_"): i for i in spdx_ids}) # type: ignore

logger = getLogger(__name__)

ENTRY_POINT = "napari.manifest"
Expand All @@ -54,19 +49,20 @@ class DiscoverResults(NamedTuple):
class PluginManifest(BaseModel):

# VS Code uses <publisher>.<name> as a unique ID for the extension
# should this just be the package name ... not the module name? (probably yes)
# do we normalize this? (i.e. underscores / dashes ?)
# should this just be the package name ... not the module name? (yes)
# do we normalize this? (i.e. underscores / dashes ?) (no)
# TODO: enforce that this matches the package name

name: str = Field(
...,
description="The name of the plugin - should be all lowercase with no spaces.",
description="The name of the plugin. Should correspond to the python "
"package name for this plugin.",
Comment on lines +61 to +62
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to the discussion in #37. I think the best approach is to require the name to be the package name. I haven't changed any of the validation so #37 is still an issue.

)
# this is not something that has an equivalent on PyPI ...
# it might be a good field with which we can identify trusted source
# but... it's not entire clear how that "trust" gets validated at the moment
publisher: Optional[str] = Field(

author: Optional[str] = Field(
None,
description="The publisher name - can be an individual or an organization",
description="The author name(s). When unspecified, the description is "
"take from the 'Author' field of the package metadata.",
)

display_name: str = Field(
Expand All @@ -77,55 +73,32 @@ class PluginManifest(BaseModel):
# non-word character.
regex=r"^[^\W_][\w -~]{1,38}[^\W_]$",
)
# take this from setup.cfg

description: Optional[str] = Field(
description="A short description of what your extension is and does."
"When unspecified, the description is taken from package metadata."
)

# TODO:
# Perhaps we should version the plugin interface (not so the manifest, but
# the actual mechanism/consumption of plugin information) independently
# of napari itself

# mechanistic things:
# this is the module that has the activate() function
# The module that has the activate() function
entry_point: Optional[str] = Field(
default=None,
description="The extension entry point. This should be a fully qualified "
"module string. e.g. `foo.bar.baz`",
description="The extension entry point. This should be a fully "
"qualified module string. e.g. `foo.bar.baz` for a module containing "
"the plugin's activate() function.",
)

# this comes from setup.cfg
version: Optional[str] = Field(None, description="SemVer compatible version.")
# this should come from setup.cfg ... but they don't requireq SPDX
license: Optional[SPDX] = None

contributions: Optional[ContributionPoints]

categories: List[str] = Field(
default_factory=list,
description="specifically defined classifiers",
)

# in the absense of input. should be inferred from version (require using rc ...)
# or use `classifiers = Status`
preview: Optional[bool] = Field(
version: Optional[str] = Field(
None,
description="Sets the extension to be flagged as a Preview in napari-hub.",
description="SemVer compatible version. When unspecified the version "
"is taken from package metadata.",
)
private: bool = Field(False, description="Whether this extension is private")

# activationEvents: Optional[List[ActivationEvent]] = Field(
# default_factory=list,
# description="Events upon which your extension becomes active.",
# )

# @validator("activationEvents", pre=True)
# def _validateActivationEvent(cls, val):
# return [
# dict(zip(("kind", "id"), x.split(":"))) if ":" in x else x
# for x in val
# ]
contributions: Optional[ContributionPoints]

_manifest_file: Optional[Path] = None

Expand Down Expand Up @@ -275,15 +248,6 @@ def _populate_missing_meta(self, metadata: Message):
self.version = metadata["Version"]
if not self.description:
self.description = metadata["Summary"]
if not self.license:
self.license = metadata["License"]
if self.preview is None:
for k, v in getattr(metadata, "_headers"):
if k.lower() == "classifier" and v.lower().startswith(
"development status"
):
self.preview = int(v.split(":: ")[-1][0]) < 3
break

@classmethod
def discover(
Expand Down
Loading