New upstream version 0.3
Julien Puydt
5 years ago
0 | 0 | language: python |
1 | 1 | python: |
2 | - "3.6" | |
3 | - "3.5" | |
2 | 4 | - "3.4" |
3 | - "3.3" | |
4 | 5 | - "2.7" |
5 | 6 | install: |
6 | 7 | - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install configparser; fi |
0 | Entry points are a way for Python packages to advertise objects with some | |
1 | common interface. The most common examples are ``console_scripts`` entry points, | |
2 | which define shell commands by identifying a Python function to run. | |
3 | ||
4 | *Groups* of entry points, such as ``console_scripts``, point to objects with | |
5 | similar interfaces. An application might use a group to find its plugins, or | |
6 | multiple groups if it has different kinds of plugins. | |
7 | ||
8 | The **entrypoints** module contains functions to find and load entry points. | |
9 | You can install it from PyPI with ``pip install entrypoints``. | |
10 | ||
11 | To advertise entry points when distributing a package, see | |
12 | `entry_points in the Python Packaging User Guide | |
13 | <https://packaging.python.org/en/latest/distributing.html#entry-points>`_. |
31 | 31 | .. attribute:: object_name |
32 | 32 | |
33 | 33 | The dotted object name within the module, or *None* if the entry point |
34 | refers to a module itselfs. | |
34 | refers to a module itself. | |
35 | 35 | |
36 | 36 | .. attribute:: extras |
37 | 37 | |
38 | 38 | Extra setuptools features related to this entry point as a list, or *None* |
39 | 39 | |
40 | .. attribute:: distribution | |
40 | .. attribute:: distro | |
41 | 41 | |
42 | 42 | The distribution which advertised this entry point - |
43 | 43 | a :class:`Distribution` instance or None |
59 | 59 | # built documents. |
60 | 60 | # |
61 | 61 | # The short X.Y version. |
62 | version = '0.2' | |
62 | from entrypoints import __version__ as version | |
63 | 63 | # The full version, including alpha/beta/rc tags. |
64 | release = version + '.3' | |
64 | release = version | |
65 | 65 | |
66 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation |
67 | 67 | # for a list of supported languages. |
0 | 0 | entrypoints |version| |
1 | 1 | ===================== |
2 | 2 | |
3 | Entry points are a way for Python packages to advertise objects with some | |
4 | common interface. The most common examples are ``console_scripts`` entry points, | |
5 | which define shell commands by identifying a Python function to run. | |
6 | ||
7 | *Groups* of entry points, such as ``console_scripts``, point to objects with | |
8 | similar interfaces. An application might use a group to find its plugins, or | |
9 | multiple groups if it has different kinds of plugins. | |
10 | ||
11 | The **entrypoints** module contains functions to find and load entry points. | |
12 | You can install it from PyPI with ``pip install entrypoints``. | |
13 | ||
14 | To advertise entry points when distributing a package, see | |
15 | `entry_points in the Python Packaging User Guide | |
16 | <https://packaging.python.org/en/latest/distributing.html#entry-points>`_. | |
3 | .. include:: ../README.rst | |
17 | 4 | |
18 | 5 | Contents: |
19 | 6 |
25 | 25 | $ |
26 | 26 | """, re.VERBOSE) |
27 | 27 | |
28 | __version__ = '0.2.3' | |
28 | file_in_zip_pattern = re.compile(r""" | |
29 | (?P<dist_version>[^/\\]+)\.(dist|egg)-info | |
30 | [/\\]entry_points.txt$ | |
31 | """, re.VERBOSE) | |
32 | ||
33 | __version__ = '0.3' | |
29 | 34 | |
30 | 35 | class BadEntryPoint(Exception): |
31 | 36 | """Raised when an entry point can't be parsed. |
79 | 84 | for attr in self.object_name.split('.'): |
80 | 85 | obj = getattr(obj, attr) |
81 | 86 | return obj |
82 | ||
87 | ||
83 | 88 | @classmethod |
84 | 89 | def from_string(cls, epstr, name, distro=None): |
85 | 90 | """Parse an entry point from the syntax in entry_points.txt |
94 | 99 | if m: |
95 | 100 | mod, obj, extras = m.group('modulename', 'objectname', 'extras') |
96 | 101 | if extras is not None: |
97 | extras = re.split(',\s*', extras) | |
102 | extras = re.split(r',\s*', extras) | |
98 | 103 | return cls(name, mod, obj, extras, distro) |
99 | 104 | else: |
100 | 105 | raise BadEntryPoint(epstr) |
103 | 108 | def __init__(self, name, version): |
104 | 109 | self.name = name |
105 | 110 | self.version = version |
106 | ||
111 | ||
107 | 112 | def __repr__(self): |
108 | 113 | return "Distribution(%r, %r)" % (self.name, self.version) |
109 | 114 | |
131 | 136 | distro_names_seen.add(distro.name) |
132 | 137 | else: |
133 | 138 | distro = None |
134 | ||
139 | ||
135 | 140 | if osp.isdir(folder): |
136 | 141 | ep_path = osp.join(folder, 'EGG-INFO', 'entry_points.txt') |
137 | 142 | if osp.isfile(ep_path): |
138 | cp = CaseSensitiveConfigParser() | |
139 | cp.read(ep_path) | |
143 | cp = CaseSensitiveConfigParser(delimiters=('=',)) | |
144 | cp.read([ep_path]) | |
140 | 145 | yield cp, distro |
141 | 146 | |
142 | 147 | elif zipfile.is_zipfile(folder): |
145 | 150 | info = z.getinfo('EGG-INFO/entry_points.txt') |
146 | 151 | except KeyError: |
147 | 152 | continue |
148 | cp = CaseSensitiveConfigParser() | |
153 | cp = CaseSensitiveConfigParser(delimiters=('=',)) | |
149 | 154 | with z.open(info) as f: |
150 | 155 | fu = io.TextIOWrapper(f) |
151 | 156 | cp.read_file(fu, |
152 | 157 | source=osp.join(folder, 'EGG-INFO', 'entry_points.txt')) |
153 | 158 | yield cp, distro |
154 | ||
159 | ||
160 | # zip imports, not egg | |
161 | elif zipfile.is_zipfile(folder): | |
162 | with zipfile.ZipFile(folder) as zf: | |
163 | for info in zf.infolist(): | |
164 | m = file_in_zip_pattern.match(info.filename) | |
165 | if not m: | |
166 | continue | |
167 | ||
168 | distro_name_version = m.group('dist_version') | |
169 | if '-' in distro_name_version: | |
170 | distro = Distribution(*distro_name_version.split('-', 1)) | |
171 | ||
172 | if (repeated_distro == 'first') \ | |
173 | and (distro.name in distro_names_seen): | |
174 | continue | |
175 | distro_names_seen.add(distro.name) | |
176 | else: | |
177 | distro = None | |
178 | ||
179 | cp = CaseSensitiveConfigParser(delimiters=('=',)) | |
180 | with zf.open(info) as f: | |
181 | fu = io.TextIOWrapper(f) | |
182 | cp.read_file(fu, source=osp.join(folder, info.filename)) | |
183 | yield cp, distro | |
184 | ||
185 | # Regular file imports (not egg, not zip file) | |
155 | 186 | for path in itertools.chain( |
156 | 187 | glob.iglob(osp.join(folder, '*.dist-info', 'entry_points.txt')), |
157 | 188 | glob.iglob(osp.join(folder, '*.egg-info', 'entry_points.txt')) |
166 | 197 | distro_names_seen.add(distro.name) |
167 | 198 | else: |
168 | 199 | distro = None |
169 | cp = CaseSensitiveConfigParser() | |
170 | cp.read(path) | |
200 | cp = CaseSensitiveConfigParser(delimiters=('=',)) | |
201 | cp.read([path]) | |
171 | 202 | yield cp, distro |
172 | 203 | |
173 | 204 | def get_single(group, name, path=None): |
0 | [metadata] | |
1 | module = entrypoints | |
2 | author = Thomas Kluyver | |
3 | author-email = thomas@kluyver.me.uk | |
4 | home-page = https://github.com/takluyver/entrypoints | |
5 | classifiers = License :: OSI Approved :: MIT License | |
6 | requires-python = >=2.7 | |
7 | requires = configparser (>=3.5); python_version == '2.7' |
0 | [build-system] | |
1 | requires = ["flit"] | |
2 | build-backend = "flit.buildapi" | |
3 | ||
4 | [tool.flit.metadata] | |
5 | module = "entrypoints" | |
6 | author = "Thomas Kluyver" | |
7 | author-email = "thomas@kluyver.me.uk" | |
8 | home-page = "https://github.com/takluyver/entrypoints" | |
9 | description-file = "README.rst" | |
10 | classifiers = [ | |
11 | "License :: OSI Approved :: MIT License", | |
12 | "Programming Language :: Python :: 2", | |
13 | "Programming Language :: Python :: 3" | |
14 | ] | |
15 | requires-python = ">=2.7" | |
16 | requires = ["configparser (>=3.5); python_version == '2.7'"] | |
17 | ||
18 | [tool.flit.metadata.urls] | |
19 | Documentation = "https://entrypoints.readthedocs.io/en/latest/" |
3 | 3 | import os.path as osp |
4 | 4 | import pytest |
5 | 5 | import warnings |
6 | from zipfile import ZipFile | |
6 | 7 | |
7 | 8 | import entrypoints |
8 | 9 | |
34 | 35 | assert ep.object_name == 'abc' |
35 | 36 | |
36 | 37 | ep2 = entrypoints.get_single('entrypoints.test1', 'njn', sample_path) |
37 | assert ep.module_name == 'foo' | |
38 | assert ep.object_name == 'abc' | |
38 | assert ep2.module_name == 'qux.extn' | |
39 | assert ep2.object_name == 'Njn.load' | |
39 | 40 | |
40 | 41 | def test_dot_prefix(): |
41 | 42 | ep = entrypoints.get_single('blogtool.parsers', '.rst', sample_path) |
48 | 49 | def test_case_sensitive(): |
49 | 50 | group = entrypoints.get_group_named('test.case_sensitive', sample_path) |
50 | 51 | assert set(group.keys()) == {'Ptangle', 'ptangle'} |
52 | ||
53 | def test_load_zip(tmpdir): | |
54 | whl_file = str(tmpdir / 'parmesan-1.2.whl') | |
55 | with ZipFile(whl_file, 'w') as whl: | |
56 | whl.writestr('parmesan-1.2.dist-info/entry_points.txt', | |
57 | b'[entrypoints.test.inzip]\na = edam:gouda') | |
58 | whl.writestr('gruyere-2!1b4.dev0.egg-info/entry_points.txt', | |
59 | b'[entrypoints.test.inzip]\nb = wensleydale:gouda') | |
60 | ||
61 | ep = entrypoints.get_single('entrypoints.test.inzip', 'a', [str(whl_file)]) | |
62 | assert ep.module_name == 'edam' | |
63 | assert ep.object_name == 'gouda' | |
64 | assert ep.distro.name == 'parmesan' | |
65 | assert ep.distro.version == '1.2' | |
66 | ||
67 | ep2 = entrypoints.get_single('entrypoints.test.inzip', 'b', [str(whl_file)]) | |
68 | assert ep2.module_name == 'wensleydale' | |
69 | assert ep2.object_name == 'gouda' | |
70 | assert ep2.distro.name == 'gruyere' | |
71 | assert ep2.distro.version == '2!1b4.dev0' | |
51 | 72 | |
52 | 73 | def test_load(): |
53 | 74 | ep = entrypoints.EntryPoint('get_ep', 'entrypoints', 'get_single', None) |