File: dist.py

package info (click to toggle)
napari-plugin-engine 0.2.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 432 kB
  • sloc: python: 3,052; makefile: 21
file content (162 lines) | stat: -rw-r--r-- 4,761 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
import inspect
import sys
from functools import lru_cache
from typing import Any, Dict, Optional, overload

if sys.version_info >= (3, 8):
    from importlib import metadata as importlib_metadata
else:
    import importlib_metadata


@lru_cache(maxsize=1)
def _top_level_module_to_dist() -> Dict[str, importlib_metadata.Distribution]:
    mapping = {}
    for dist in importlib_metadata.distributions():
        modules = dist.read_text('top_level.txt')
        if modules:
            for mod in modules.split('\n'):
                if not mod:
                    continue
                mapping[mod] = dist
    return mapping


def _object_to_top_level_module(obj: Any) -> Optional[str]:
    module = inspect.getmodule(obj)
    name = getattr(module, '__name__', None)
    return name.split('.')[0] if name else None


def get_dist(obj) -> Optional[importlib_metadata.Distribution]:
    """Return a :class:`importlib.metadata.Distribution` for any python object.

    Parameters
    ----------
    obj : Any
        A python object. If a string, will be interpreted as a dist name.

    Returns
    -------
    dist: Distribution
        The distribution object for the corresponding package, if found.
    """
    if isinstance(obj, str):
        try:
            return importlib_metadata.distribution(obj)
        except importlib_metadata.PackageNotFoundError:
            return None
    top_level = _object_to_top_level_module(obj)
    return _top_level_module_to_dist().get(top_level or '')


def get_version(plugin) -> str:
    version = ''
    dist = get_dist(plugin)
    if dist:
        version = dist.metadata.get('version')
    if not version and inspect.ismodule(plugin):
        version = getattr(plugin, '__version__', None)
    if not version:
        top_module = _object_to_top_level_module(plugin)
        if top_module in sys.modules:
            version = getattr(sys.modules[top_module], '__version__', None)
    return str(version) if version else ''


@overload
def get_metadata(plugin, arg: str, *args: None) -> Optional[str]:
    ...


@overload  # noqa: F811
def get_metadata(  # noqa: F811
    plugin, arg: str, *args: str
) -> Dict[str, Optional[str]]:
    ...


def get_metadata(plugin, *args):  # noqa: F811
    """Get metadata for this plugin.

    Valid arguments are any keys from the Core metadata specifications:
    https://packaging.python.org/specifications/core-metadata/

    Parameters
    ----------
    *args : str
        (Case insensitive) names of metadata entries to retrieve.

    Returns
    -------
    str or dict, optional
        If a single argument is provided, the value for that entry is
        returned (which may be ``None``).
        If multiple arguments are provided, a dict of {arg: value} is
        returned.
    """
    dist = get_dist(plugin)
    dct = {}
    if dist:
        for a in args:
            if a == 'version':
                dct[a] = get_version(plugin)
            else:
                dct[a] = dist.metadata.get(a)
    if len(args) == 1:
        return dct[args[0]] if dct else None
    return dct


def standard_metadata(plugin: Any) -> Dict[str, Optional[str]]:
    """Return a standard metadata dict for ``plugin``.

    Parameters
    ----------
    plugin : Any
        A python object.  If a string, will be interpreted as a dist name.

    Returns
    -------
    metadata : dict
        A  dicts with plugin object metadata. The dict is guaranteed to have
        the following keys:

        - **package**: The name of the package
        - **version**: The version of the plugin package
        - **summary**: A one-line summary of what the distribution does
        - **author**: The author’s name
        - **email**: The author’s (or maintainer's) e-mail address.
        - **license**: The license covering the distribution
        - **url**: The home page for the package, or dowload url if N/A.

    Raises
    ------
    ValueError
        If no distribution can be found for ``plugin``.
    """
    meta = {}
    if not get_dist(plugin):
        _top_level_module_to_dist.cache_clear()
        if not get_dist(plugin):
            return {}
            # TODO: decide appropriate behavior here.
            # raise ValueError(f"could not find metadata for {plugin}")
    meta = get_metadata(
        plugin,
        'name',
        'version',
        'summary',
        'author',
        'license',
        'Author-Email',
        'Home-page',
    )
    meta['package'] = meta.pop('name')
    meta['email'] = meta.pop('Author-Email') or get_metadata(
        plugin, 'Maintainer-Email'
    )
    meta['url'] = meta.pop('Home-page') or get_metadata(plugin, 'Download-Url')
    if meta['url'] == 'UNKNOWN':
        meta['url'] = None
    return meta