cs-py-modules 20240630__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cs.py.modules-20240630/MANIFEST.in +1 -0
- cs.py.modules-20240630/PKG-INFO +146 -0
- cs.py.modules-20240630/README.md +125 -0
- cs.py.modules-20240630/lib/python/cs/py/modules.py +227 -0
- cs.py.modules-20240630/lib/python/cs.py.modules.egg-info/PKG-INFO +146 -0
- cs.py.modules-20240630/lib/python/cs.py.modules.egg-info/SOURCES.txt +9 -0
- cs.py.modules-20240630/lib/python/cs.py.modules.egg-info/dependency_links.txt +1 -0
- cs.py.modules-20240630/lib/python/cs.py.modules.egg-info/requires.txt +3 -0
- cs.py.modules-20240630/lib/python/cs.py.modules.egg-info/top_level.txt +1 -0
- cs.py.modules-20240630/pyproject.toml +180 -0
- cs.py.modules-20240630/setup.cfg +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include README.md
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cs.py.modules
|
|
3
|
+
Version: 20240630
|
|
4
|
+
Summary: Convenience functions related to modules and importing.
|
|
5
|
+
Author-email: Cameron Simpson <cs@cskk.id.au>
|
|
6
|
+
License: GNU General Public License v3 or later (GPLv3+)
|
|
7
|
+
Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
|
|
8
|
+
Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
|
|
9
|
+
Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
|
|
10
|
+
Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/py/modules.py
|
|
11
|
+
Keywords: python2,python3
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 2
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
Convenience functions related to modules and importing.
|
|
23
|
+
|
|
24
|
+
*Latest release 20240630*:
|
|
25
|
+
direct_imports: fix off-by-one resolving leading dot relative import names.
|
|
26
|
+
|
|
27
|
+
## Function `direct_imports(src_filename, module_name=None)`
|
|
28
|
+
|
|
29
|
+
Crudely parse `src_filename` for `import` statements.
|
|
30
|
+
Return the set of directly imported module names.
|
|
31
|
+
|
|
32
|
+
If `module_name` is not `None`,
|
|
33
|
+
resolve relative imports against it.
|
|
34
|
+
Otherwise, relative import names are returned unresolved.
|
|
35
|
+
|
|
36
|
+
This is a very simple minded source parse.
|
|
37
|
+
|
|
38
|
+
## Function `import_extra(extra_package_name, distinfo)`
|
|
39
|
+
|
|
40
|
+
Try to import the package named `extra_package_name`
|
|
41
|
+
using `importlib.import_module`. Return the imported package.
|
|
42
|
+
|
|
43
|
+
If an `ImportError` is raised,
|
|
44
|
+
riffle through the extras mapping in `distinfo['extras_requires']`
|
|
45
|
+
for the package name, and emit an informative warning
|
|
46
|
+
about the extras which require this package
|
|
47
|
+
and whose use a `pip install` time would bring the package in.
|
|
48
|
+
The original `ImportError` is then reraised.
|
|
49
|
+
|
|
50
|
+
If no extra is found this is presumed to be an error by the caller
|
|
51
|
+
and a `RuntimeError` is raised.
|
|
52
|
+
This function is for internal use as:
|
|
53
|
+
|
|
54
|
+
pkg = import_extra('some_package', DISTINFO)
|
|
55
|
+
|
|
56
|
+
which passes in the source module's `DISTINFO` mapping,
|
|
57
|
+
which I use as the basis for my package distributions.
|
|
58
|
+
|
|
59
|
+
A fuller example from my `cs.timeseries` module's
|
|
60
|
+
`plot` command line mode:
|
|
61
|
+
|
|
62
|
+
def cmd_plot(self, argv):
|
|
63
|
+
""" Usage: {cmd} datadir days fields...
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
import_extra('plotly', DISTINFO)
|
|
67
|
+
except ImportError as e:
|
|
68
|
+
raise GetoptError(
|
|
69
|
+
"the plotly package is not installed: %s" % (e,)
|
|
70
|
+
) from e
|
|
71
|
+
|
|
72
|
+
which produces this output:
|
|
73
|
+
|
|
74
|
+
timeseries.py: plot: import_extra('plotly'): package not available; the following extras pull it in: ['plotting']
|
|
75
|
+
timeseries.py: the plotly package is not installed: timeseries.py: plot: import_extra('plotly'): No module named 'plotly'
|
|
76
|
+
|
|
77
|
+
## Function `import_module_from_file(module_name, source_file, sys_path=None)`
|
|
78
|
+
|
|
79
|
+
Import a specific file as a module instance,
|
|
80
|
+
return the module instance.
|
|
81
|
+
|
|
82
|
+
Parameters:
|
|
83
|
+
* `module_name`: the name to assign to the module
|
|
84
|
+
* `source_file`: the source file to load
|
|
85
|
+
* `sys_path`: optional list of paths to set as `sys.path`
|
|
86
|
+
for the duration of this import;
|
|
87
|
+
the default is the current value of `sys.path`
|
|
88
|
+
|
|
89
|
+
Note that this is a "bare" import;
|
|
90
|
+
the module instance is not inserted into `sys.modules`.
|
|
91
|
+
|
|
92
|
+
*Warning*: `sys.path` is modified for the duration of this function,
|
|
93
|
+
which may affect multithreaded applications.
|
|
94
|
+
|
|
95
|
+
## Function `import_module_name(module_name, name, path=None, lock=None)`
|
|
96
|
+
|
|
97
|
+
Import `module_name` and return the value of `name` within it.
|
|
98
|
+
|
|
99
|
+
Parameters:
|
|
100
|
+
* `module_name`: the module name to import.
|
|
101
|
+
* `name`: the name within the module whose value is returned;
|
|
102
|
+
if `name` is `None`, return the module itself.
|
|
103
|
+
* `path`: an array of paths to use as sys.path during the import.
|
|
104
|
+
* `lock`: a lock to hold during the import (recommended).
|
|
105
|
+
|
|
106
|
+
## Function `module_attributes(M)`
|
|
107
|
+
|
|
108
|
+
Generator yielding the names and values of attributes from a module
|
|
109
|
+
which were defined in the module.
|
|
110
|
+
|
|
111
|
+
## Function `module_files(M)`
|
|
112
|
+
|
|
113
|
+
Generator yielding `.py` pathnames involved in a module.
|
|
114
|
+
|
|
115
|
+
## Function `module_names(M)`
|
|
116
|
+
|
|
117
|
+
Return a list of the names of attributes from a module which were
|
|
118
|
+
defined in the module.
|
|
119
|
+
|
|
120
|
+
# Release Log
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
*Release 20240630*:
|
|
125
|
+
direct_imports: fix off-by-one resolving leading dot relative import names.
|
|
126
|
+
|
|
127
|
+
*Release 20220606*:
|
|
128
|
+
New import_extra(extra_package_name,distinfo) function to politely try to import a package which is associated with an extra.
|
|
129
|
+
|
|
130
|
+
*Release 20210123*:
|
|
131
|
+
module_attributes: skip values from other modules _if we know the module_ (computed values like tuples have no module and still need to be returned).
|
|
132
|
+
|
|
133
|
+
*Release 20200521*:
|
|
134
|
+
* New import_module_from_file function to import a Python file as a module instance.
|
|
135
|
+
* New direct_imports(src_filename,module_name=None) returning the set of directly imports module names.
|
|
136
|
+
|
|
137
|
+
*Release 20190101*:
|
|
138
|
+
New functions: module_names, module_attributes.
|
|
139
|
+
|
|
140
|
+
*Release 20160918*:
|
|
141
|
+
* New generator function module_files yielding pathnames.
|
|
142
|
+
* import_module_name: accept name=None, just return the module.
|
|
143
|
+
* Add empty "install_requires" for DISTINFO completeness.
|
|
144
|
+
|
|
145
|
+
*Release 20150116*:
|
|
146
|
+
Initial PyPI release.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Convenience functions related to modules and importing.
|
|
2
|
+
|
|
3
|
+
*Latest release 20240630*:
|
|
4
|
+
direct_imports: fix off-by-one resolving leading dot relative import names.
|
|
5
|
+
|
|
6
|
+
## Function `direct_imports(src_filename, module_name=None)`
|
|
7
|
+
|
|
8
|
+
Crudely parse `src_filename` for `import` statements.
|
|
9
|
+
Return the set of directly imported module names.
|
|
10
|
+
|
|
11
|
+
If `module_name` is not `None`,
|
|
12
|
+
resolve relative imports against it.
|
|
13
|
+
Otherwise, relative import names are returned unresolved.
|
|
14
|
+
|
|
15
|
+
This is a very simple minded source parse.
|
|
16
|
+
|
|
17
|
+
## Function `import_extra(extra_package_name, distinfo)`
|
|
18
|
+
|
|
19
|
+
Try to import the package named `extra_package_name`
|
|
20
|
+
using `importlib.import_module`. Return the imported package.
|
|
21
|
+
|
|
22
|
+
If an `ImportError` is raised,
|
|
23
|
+
riffle through the extras mapping in `distinfo['extras_requires']`
|
|
24
|
+
for the package name, and emit an informative warning
|
|
25
|
+
about the extras which require this package
|
|
26
|
+
and whose use a `pip install` time would bring the package in.
|
|
27
|
+
The original `ImportError` is then reraised.
|
|
28
|
+
|
|
29
|
+
If no extra is found this is presumed to be an error by the caller
|
|
30
|
+
and a `RuntimeError` is raised.
|
|
31
|
+
This function is for internal use as:
|
|
32
|
+
|
|
33
|
+
pkg = import_extra('some_package', DISTINFO)
|
|
34
|
+
|
|
35
|
+
which passes in the source module's `DISTINFO` mapping,
|
|
36
|
+
which I use as the basis for my package distributions.
|
|
37
|
+
|
|
38
|
+
A fuller example from my `cs.timeseries` module's
|
|
39
|
+
`plot` command line mode:
|
|
40
|
+
|
|
41
|
+
def cmd_plot(self, argv):
|
|
42
|
+
""" Usage: {cmd} datadir days fields...
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
import_extra('plotly', DISTINFO)
|
|
46
|
+
except ImportError as e:
|
|
47
|
+
raise GetoptError(
|
|
48
|
+
"the plotly package is not installed: %s" % (e,)
|
|
49
|
+
) from e
|
|
50
|
+
|
|
51
|
+
which produces this output:
|
|
52
|
+
|
|
53
|
+
timeseries.py: plot: import_extra('plotly'): package not available; the following extras pull it in: ['plotting']
|
|
54
|
+
timeseries.py: the plotly package is not installed: timeseries.py: plot: import_extra('plotly'): No module named 'plotly'
|
|
55
|
+
|
|
56
|
+
## Function `import_module_from_file(module_name, source_file, sys_path=None)`
|
|
57
|
+
|
|
58
|
+
Import a specific file as a module instance,
|
|
59
|
+
return the module instance.
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
* `module_name`: the name to assign to the module
|
|
63
|
+
* `source_file`: the source file to load
|
|
64
|
+
* `sys_path`: optional list of paths to set as `sys.path`
|
|
65
|
+
for the duration of this import;
|
|
66
|
+
the default is the current value of `sys.path`
|
|
67
|
+
|
|
68
|
+
Note that this is a "bare" import;
|
|
69
|
+
the module instance is not inserted into `sys.modules`.
|
|
70
|
+
|
|
71
|
+
*Warning*: `sys.path` is modified for the duration of this function,
|
|
72
|
+
which may affect multithreaded applications.
|
|
73
|
+
|
|
74
|
+
## Function `import_module_name(module_name, name, path=None, lock=None)`
|
|
75
|
+
|
|
76
|
+
Import `module_name` and return the value of `name` within it.
|
|
77
|
+
|
|
78
|
+
Parameters:
|
|
79
|
+
* `module_name`: the module name to import.
|
|
80
|
+
* `name`: the name within the module whose value is returned;
|
|
81
|
+
if `name` is `None`, return the module itself.
|
|
82
|
+
* `path`: an array of paths to use as sys.path during the import.
|
|
83
|
+
* `lock`: a lock to hold during the import (recommended).
|
|
84
|
+
|
|
85
|
+
## Function `module_attributes(M)`
|
|
86
|
+
|
|
87
|
+
Generator yielding the names and values of attributes from a module
|
|
88
|
+
which were defined in the module.
|
|
89
|
+
|
|
90
|
+
## Function `module_files(M)`
|
|
91
|
+
|
|
92
|
+
Generator yielding `.py` pathnames involved in a module.
|
|
93
|
+
|
|
94
|
+
## Function `module_names(M)`
|
|
95
|
+
|
|
96
|
+
Return a list of the names of attributes from a module which were
|
|
97
|
+
defined in the module.
|
|
98
|
+
|
|
99
|
+
# Release Log
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
*Release 20240630*:
|
|
104
|
+
direct_imports: fix off-by-one resolving leading dot relative import names.
|
|
105
|
+
|
|
106
|
+
*Release 20220606*:
|
|
107
|
+
New import_extra(extra_package_name,distinfo) function to politely try to import a package which is associated with an extra.
|
|
108
|
+
|
|
109
|
+
*Release 20210123*:
|
|
110
|
+
module_attributes: skip values from other modules _if we know the module_ (computed values like tuples have no module and still need to be returned).
|
|
111
|
+
|
|
112
|
+
*Release 20200521*:
|
|
113
|
+
* New import_module_from_file function to import a Python file as a module instance.
|
|
114
|
+
* New direct_imports(src_filename,module_name=None) returning the set of directly imports module names.
|
|
115
|
+
|
|
116
|
+
*Release 20190101*:
|
|
117
|
+
New functions: module_names, module_attributes.
|
|
118
|
+
|
|
119
|
+
*Release 20160918*:
|
|
120
|
+
* New generator function module_files yielding pathnames.
|
|
121
|
+
* import_module_name: accept name=None, just return the module.
|
|
122
|
+
* Add empty "install_requires" for DISTINFO completeness.
|
|
123
|
+
|
|
124
|
+
*Release 20150116*:
|
|
125
|
+
Initial PyPI release.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
#
|
|
3
|
+
|
|
4
|
+
''' Convenience functions related to modules and importing.
|
|
5
|
+
'''
|
|
6
|
+
|
|
7
|
+
import importlib
|
|
8
|
+
from importlib.machinery import SourceFileLoader
|
|
9
|
+
from importlib.util import spec_from_loader, module_from_spec
|
|
10
|
+
from inspect import getmodule
|
|
11
|
+
import os.path
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from cs.context import stackattrs
|
|
15
|
+
from cs.gimmicks import warning
|
|
16
|
+
from cs.pfx import Pfx
|
|
17
|
+
|
|
18
|
+
__version__ = '20240630'
|
|
19
|
+
|
|
20
|
+
DISTINFO = {
|
|
21
|
+
'keywords': ["python2", "python3"],
|
|
22
|
+
'classifiers': [
|
|
23
|
+
"Programming Language :: Python",
|
|
24
|
+
"Programming Language :: Python :: 2",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
],
|
|
27
|
+
'install_requires': ['cs.context', 'cs.gimmicks', 'cs.pfx'],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def import_module_name(module_name, name, path=None, lock=None):
|
|
31
|
+
''' Import `module_name` and return the value of `name` within it.
|
|
32
|
+
|
|
33
|
+
Parameters:
|
|
34
|
+
* `module_name`: the module name to import.
|
|
35
|
+
* `name`: the name within the module whose value is returned;
|
|
36
|
+
if `name` is `None`, return the module itself.
|
|
37
|
+
* `path`: an array of paths to use as sys.path during the import.
|
|
38
|
+
* `lock`: a lock to hold during the import (recommended).
|
|
39
|
+
'''
|
|
40
|
+
if lock:
|
|
41
|
+
with lock:
|
|
42
|
+
return import_module_name(module_name, name, path)
|
|
43
|
+
osyspath = sys.path
|
|
44
|
+
if path:
|
|
45
|
+
sys.path = path
|
|
46
|
+
try:
|
|
47
|
+
M = importlib.import_module(module_name)
|
|
48
|
+
except ImportError as e:
|
|
49
|
+
# pylint: disable=raise-missing-from
|
|
50
|
+
raise ImportError("no module named %r: %s: %s" % (module_name, type(e), e))
|
|
51
|
+
finally:
|
|
52
|
+
if path:
|
|
53
|
+
sys.path = osyspath
|
|
54
|
+
if M is not None:
|
|
55
|
+
if name is None:
|
|
56
|
+
return M
|
|
57
|
+
try:
|
|
58
|
+
return getattr(M, name)
|
|
59
|
+
except AttributeError as e:
|
|
60
|
+
# pylint: disable=raise-missing-from
|
|
61
|
+
raise ImportError(
|
|
62
|
+
"%s: no entry named %r: %s: %s" % (module_name, name, type(e), e)
|
|
63
|
+
)
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def import_module_from_file(module_name, source_file, sys_path=None):
|
|
67
|
+
''' Import a specific file as a module instance,
|
|
68
|
+
return the module instance.
|
|
69
|
+
|
|
70
|
+
Parameters:
|
|
71
|
+
* `module_name`: the name to assign to the module
|
|
72
|
+
* `source_file`: the source file to load
|
|
73
|
+
* `sys_path`: optional list of paths to set as `sys.path`
|
|
74
|
+
for the duration of this import;
|
|
75
|
+
the default is the current value of `sys.path`
|
|
76
|
+
|
|
77
|
+
Note that this is a "bare" import;
|
|
78
|
+
the module instance is not inserted into `sys.modules`.
|
|
79
|
+
|
|
80
|
+
*Warning*: `sys.path` is modified for the duration of this function,
|
|
81
|
+
which may affect multithreaded applications.
|
|
82
|
+
'''
|
|
83
|
+
if sys_path is None:
|
|
84
|
+
sys_path = sys.path
|
|
85
|
+
with stackattrs(sys, path=sys_path):
|
|
86
|
+
loader = SourceFileLoader(module_name, source_file)
|
|
87
|
+
spec = spec_from_loader(loader.name, loader)
|
|
88
|
+
M = module_from_spec(spec)
|
|
89
|
+
loader.exec_module(M)
|
|
90
|
+
return M
|
|
91
|
+
|
|
92
|
+
def module_files(M):
|
|
93
|
+
''' Generator yielding `.py` pathnames involved in a module.
|
|
94
|
+
'''
|
|
95
|
+
initpath = M.__file__
|
|
96
|
+
moddir = os.path.dirname(initpath)
|
|
97
|
+
for dirpath, _, filenames in os.walk(moddir):
|
|
98
|
+
for filename in filenames:
|
|
99
|
+
if filename.endswith('.py'):
|
|
100
|
+
yield os.path.join(dirpath, filename)
|
|
101
|
+
|
|
102
|
+
def module_attributes(M):
|
|
103
|
+
''' Generator yielding the names and values of attributes from a module
|
|
104
|
+
which were defined in the module.
|
|
105
|
+
'''
|
|
106
|
+
for attr in dir(M):
|
|
107
|
+
value = getattr(M, attr, None)
|
|
108
|
+
valueM = getmodule(value)
|
|
109
|
+
if valueM is not None and valueM is not M:
|
|
110
|
+
continue
|
|
111
|
+
yield attr, value
|
|
112
|
+
|
|
113
|
+
def module_names(M):
|
|
114
|
+
''' Return a list of the names of attributes from a module which were
|
|
115
|
+
defined in the module.
|
|
116
|
+
'''
|
|
117
|
+
return [attr for attr, value in module_attributes(M)]
|
|
118
|
+
|
|
119
|
+
# pylint: disable=too-many-branches
|
|
120
|
+
def direct_imports(src_filename, module_name=None):
|
|
121
|
+
''' Crudely parse `src_filename` for `import` statements.
|
|
122
|
+
Return the set of directly imported module names.
|
|
123
|
+
|
|
124
|
+
If `module_name` is not `None`,
|
|
125
|
+
resolve relative imports against it.
|
|
126
|
+
Otherwise, relative import names are returned unresolved.
|
|
127
|
+
|
|
128
|
+
This is a very simple minded source parse.
|
|
129
|
+
'''
|
|
130
|
+
subnames = set()
|
|
131
|
+
with Pfx(src_filename):
|
|
132
|
+
with open(src_filename, encoding='utf-8') as codefp:
|
|
133
|
+
for lineno, line in enumerate(codefp, 1):
|
|
134
|
+
with Pfx(lineno):
|
|
135
|
+
if line.startswith('import ') or line.startswith('from '):
|
|
136
|
+
line = line.strip()
|
|
137
|
+
# quick hack to strip trailing "; second-statement"
|
|
138
|
+
try:
|
|
139
|
+
line, _ = line.split(';', 1)
|
|
140
|
+
except ValueError:
|
|
141
|
+
pass
|
|
142
|
+
words = line.split()
|
|
143
|
+
if not words:
|
|
144
|
+
continue
|
|
145
|
+
word0 = words[0]
|
|
146
|
+
if word0 not in ('from', 'import'):
|
|
147
|
+
continue
|
|
148
|
+
if len(words) < 2:
|
|
149
|
+
continue
|
|
150
|
+
if word0 == 'from' and (len(words) < 4 or words[2] != 'import'):
|
|
151
|
+
continue
|
|
152
|
+
subimport = words[1]
|
|
153
|
+
if module_name and subimport.startswith('.'):
|
|
154
|
+
if subimport == '.':
|
|
155
|
+
subimport = module_name
|
|
156
|
+
else:
|
|
157
|
+
# resolve relative import name
|
|
158
|
+
subimport = subimport[1:]
|
|
159
|
+
module_parts = module_name.split('.')
|
|
160
|
+
while subimport.startswith('.'):
|
|
161
|
+
module_parts.pop(-1)
|
|
162
|
+
subimport = subimport[1:]
|
|
163
|
+
if module_parts:
|
|
164
|
+
if subimport:
|
|
165
|
+
module_parts.append(subimport)
|
|
166
|
+
subimport = '.'.join(module_parts)
|
|
167
|
+
subnames.add(subimport)
|
|
168
|
+
return subnames
|
|
169
|
+
|
|
170
|
+
def import_extra(extra_package_name, distinfo):
|
|
171
|
+
''' Try to import the package named `extra_package_name`
|
|
172
|
+
using `importlib.import_module`. Return the imported package.
|
|
173
|
+
|
|
174
|
+
If an `ImportError` is raised,
|
|
175
|
+
riffle through the extras mapping in `distinfo['extras_requires']`
|
|
176
|
+
for the package name, and emit an informative warning
|
|
177
|
+
about the extras which require this package
|
|
178
|
+
and whose use a `pip install` time would bring the package in.
|
|
179
|
+
The original `ImportError` is then reraised.
|
|
180
|
+
|
|
181
|
+
If no extra is found this is presumed to be an error by the caller
|
|
182
|
+
and a `RuntimeError` is raised.
|
|
183
|
+
This function is for internal use as:
|
|
184
|
+
|
|
185
|
+
pkg = import_extra('some_package', DISTINFO)
|
|
186
|
+
|
|
187
|
+
which passes in the source module's `DISTINFO` mapping,
|
|
188
|
+
which I use as the basis for my package distributions.
|
|
189
|
+
|
|
190
|
+
A fuller example from my `cs.timeseries` module's
|
|
191
|
+
`plot` command line mode:
|
|
192
|
+
|
|
193
|
+
def cmd_plot(self, argv):
|
|
194
|
+
""" Usage: {cmd} datadir days fields...
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
import_extra('plotly', DISTINFO)
|
|
198
|
+
except ImportError as e:
|
|
199
|
+
raise GetoptError(
|
|
200
|
+
"the plotly package is not installed: %s" % (e,)
|
|
201
|
+
) from e
|
|
202
|
+
|
|
203
|
+
which produces this output:
|
|
204
|
+
|
|
205
|
+
timeseries.py: plot: import_extra('plotly'): package not available; the following extras pull it in: ['plotting']
|
|
206
|
+
timeseries.py: the plotly package is not installed: timeseries.py: plot: import_extra('plotly'): No module named 'plotly'
|
|
207
|
+
'''
|
|
208
|
+
with Pfx("import_extra(%r)", extra_package_name):
|
|
209
|
+
try:
|
|
210
|
+
return importlib.import_module(extra_package_name)
|
|
211
|
+
except ImportError:
|
|
212
|
+
from_extras = [
|
|
213
|
+
extra_name
|
|
214
|
+
for extra_name, extra_packages in distinfo['extras_requires'].items()
|
|
215
|
+
if extra_package_name in extra_packages
|
|
216
|
+
]
|
|
217
|
+
if from_extras:
|
|
218
|
+
warning(
|
|
219
|
+
"package not available; the following extras pull it in: %r" %
|
|
220
|
+
(sorted(from_extras),)
|
|
221
|
+
)
|
|
222
|
+
raise
|
|
223
|
+
# pylint: disable=raise-missing-from
|
|
224
|
+
raise RuntimeError(
|
|
225
|
+
"import_extra called with a package not listed in DISTINFO[extras_requires]=%r"
|
|
226
|
+
% (DISTINFO['extras_requires'],)
|
|
227
|
+
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cs.py.modules
|
|
3
|
+
Version: 20240630
|
|
4
|
+
Summary: Convenience functions related to modules and importing.
|
|
5
|
+
Author-email: Cameron Simpson <cs@cskk.id.au>
|
|
6
|
+
License: GNU General Public License v3 or later (GPLv3+)
|
|
7
|
+
Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
|
|
8
|
+
Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
|
|
9
|
+
Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
|
|
10
|
+
Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/py/modules.py
|
|
11
|
+
Keywords: python2,python3
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 2
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
Convenience functions related to modules and importing.
|
|
23
|
+
|
|
24
|
+
*Latest release 20240630*:
|
|
25
|
+
direct_imports: fix off-by-one resolving leading dot relative import names.
|
|
26
|
+
|
|
27
|
+
## Function `direct_imports(src_filename, module_name=None)`
|
|
28
|
+
|
|
29
|
+
Crudely parse `src_filename` for `import` statements.
|
|
30
|
+
Return the set of directly imported module names.
|
|
31
|
+
|
|
32
|
+
If `module_name` is not `None`,
|
|
33
|
+
resolve relative imports against it.
|
|
34
|
+
Otherwise, relative import names are returned unresolved.
|
|
35
|
+
|
|
36
|
+
This is a very simple minded source parse.
|
|
37
|
+
|
|
38
|
+
## Function `import_extra(extra_package_name, distinfo)`
|
|
39
|
+
|
|
40
|
+
Try to import the package named `extra_package_name`
|
|
41
|
+
using `importlib.import_module`. Return the imported package.
|
|
42
|
+
|
|
43
|
+
If an `ImportError` is raised,
|
|
44
|
+
riffle through the extras mapping in `distinfo['extras_requires']`
|
|
45
|
+
for the package name, and emit an informative warning
|
|
46
|
+
about the extras which require this package
|
|
47
|
+
and whose use a `pip install` time would bring the package in.
|
|
48
|
+
The original `ImportError` is then reraised.
|
|
49
|
+
|
|
50
|
+
If no extra is found this is presumed to be an error by the caller
|
|
51
|
+
and a `RuntimeError` is raised.
|
|
52
|
+
This function is for internal use as:
|
|
53
|
+
|
|
54
|
+
pkg = import_extra('some_package', DISTINFO)
|
|
55
|
+
|
|
56
|
+
which passes in the source module's `DISTINFO` mapping,
|
|
57
|
+
which I use as the basis for my package distributions.
|
|
58
|
+
|
|
59
|
+
A fuller example from my `cs.timeseries` module's
|
|
60
|
+
`plot` command line mode:
|
|
61
|
+
|
|
62
|
+
def cmd_plot(self, argv):
|
|
63
|
+
""" Usage: {cmd} datadir days fields...
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
import_extra('plotly', DISTINFO)
|
|
67
|
+
except ImportError as e:
|
|
68
|
+
raise GetoptError(
|
|
69
|
+
"the plotly package is not installed: %s" % (e,)
|
|
70
|
+
) from e
|
|
71
|
+
|
|
72
|
+
which produces this output:
|
|
73
|
+
|
|
74
|
+
timeseries.py: plot: import_extra('plotly'): package not available; the following extras pull it in: ['plotting']
|
|
75
|
+
timeseries.py: the plotly package is not installed: timeseries.py: plot: import_extra('plotly'): No module named 'plotly'
|
|
76
|
+
|
|
77
|
+
## Function `import_module_from_file(module_name, source_file, sys_path=None)`
|
|
78
|
+
|
|
79
|
+
Import a specific file as a module instance,
|
|
80
|
+
return the module instance.
|
|
81
|
+
|
|
82
|
+
Parameters:
|
|
83
|
+
* `module_name`: the name to assign to the module
|
|
84
|
+
* `source_file`: the source file to load
|
|
85
|
+
* `sys_path`: optional list of paths to set as `sys.path`
|
|
86
|
+
for the duration of this import;
|
|
87
|
+
the default is the current value of `sys.path`
|
|
88
|
+
|
|
89
|
+
Note that this is a "bare" import;
|
|
90
|
+
the module instance is not inserted into `sys.modules`.
|
|
91
|
+
|
|
92
|
+
*Warning*: `sys.path` is modified for the duration of this function,
|
|
93
|
+
which may affect multithreaded applications.
|
|
94
|
+
|
|
95
|
+
## Function `import_module_name(module_name, name, path=None, lock=None)`
|
|
96
|
+
|
|
97
|
+
Import `module_name` and return the value of `name` within it.
|
|
98
|
+
|
|
99
|
+
Parameters:
|
|
100
|
+
* `module_name`: the module name to import.
|
|
101
|
+
* `name`: the name within the module whose value is returned;
|
|
102
|
+
if `name` is `None`, return the module itself.
|
|
103
|
+
* `path`: an array of paths to use as sys.path during the import.
|
|
104
|
+
* `lock`: a lock to hold during the import (recommended).
|
|
105
|
+
|
|
106
|
+
## Function `module_attributes(M)`
|
|
107
|
+
|
|
108
|
+
Generator yielding the names and values of attributes from a module
|
|
109
|
+
which were defined in the module.
|
|
110
|
+
|
|
111
|
+
## Function `module_files(M)`
|
|
112
|
+
|
|
113
|
+
Generator yielding `.py` pathnames involved in a module.
|
|
114
|
+
|
|
115
|
+
## Function `module_names(M)`
|
|
116
|
+
|
|
117
|
+
Return a list of the names of attributes from a module which were
|
|
118
|
+
defined in the module.
|
|
119
|
+
|
|
120
|
+
# Release Log
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
*Release 20240630*:
|
|
125
|
+
direct_imports: fix off-by-one resolving leading dot relative import names.
|
|
126
|
+
|
|
127
|
+
*Release 20220606*:
|
|
128
|
+
New import_extra(extra_package_name,distinfo) function to politely try to import a package which is associated with an extra.
|
|
129
|
+
|
|
130
|
+
*Release 20210123*:
|
|
131
|
+
module_attributes: skip values from other modules _if we know the module_ (computed values like tuples have no module and still need to be returned).
|
|
132
|
+
|
|
133
|
+
*Release 20200521*:
|
|
134
|
+
* New import_module_from_file function to import a Python file as a module instance.
|
|
135
|
+
* New direct_imports(src_filename,module_name=None) returning the set of directly imports module names.
|
|
136
|
+
|
|
137
|
+
*Release 20190101*:
|
|
138
|
+
New functions: module_names, module_attributes.
|
|
139
|
+
|
|
140
|
+
*Release 20160918*:
|
|
141
|
+
* New generator function module_files yielding pathnames.
|
|
142
|
+
* import_module_name: accept name=None, just return the module.
|
|
143
|
+
* Add empty "install_requires" for DISTINFO completeness.
|
|
144
|
+
|
|
145
|
+
*Release 20150116*:
|
|
146
|
+
Initial PyPI release.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
lib/python/cs.py.modules.egg-info/PKG-INFO
|
|
5
|
+
lib/python/cs.py.modules.egg-info/SOURCES.txt
|
|
6
|
+
lib/python/cs.py.modules.egg-info/dependency_links.txt
|
|
7
|
+
lib/python/cs.py.modules.egg-info/requires.txt
|
|
8
|
+
lib/python/cs.py.modules.egg-info/top_level.txt
|
|
9
|
+
lib/python/cs/py/modules.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cs
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cs.py.modules"
|
|
3
|
+
description = "Convenience functions related to modules and importing."
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Cameron Simpson", email = "cs@cskk.id.au" },
|
|
6
|
+
]
|
|
7
|
+
keywords = [
|
|
8
|
+
"python2",
|
|
9
|
+
"python3",
|
|
10
|
+
]
|
|
11
|
+
dependencies = [
|
|
12
|
+
"cs.context>=20240630",
|
|
13
|
+
"cs.gimmicks>=20240316",
|
|
14
|
+
"cs.pfx>=20240630",
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python",
|
|
18
|
+
"Programming Language :: Python :: 2",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
25
|
+
]
|
|
26
|
+
version = "20240630"
|
|
27
|
+
|
|
28
|
+
[project.license]
|
|
29
|
+
text = "GNU General Public License v3 or later (GPLv3+)"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
"Monorepo Hg/Mercurial Mirror" = "https://hg.sr.ht/~cameron-simpson/css"
|
|
33
|
+
"Monorepo Git Mirror" = "https://github.com/cameron-simpson/css"
|
|
34
|
+
"MonoRepo Commits" = "https://bitbucket.org/cameron_simpson/css/commits/branch/main"
|
|
35
|
+
Source = "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/py/modules.py"
|
|
36
|
+
|
|
37
|
+
[project.readme]
|
|
38
|
+
text = """
|
|
39
|
+
Convenience functions related to modules and importing.
|
|
40
|
+
|
|
41
|
+
*Latest release 20240630*:
|
|
42
|
+
direct_imports: fix off-by-one resolving leading dot relative import names.
|
|
43
|
+
|
|
44
|
+
## Function `direct_imports(src_filename, module_name=None)`
|
|
45
|
+
|
|
46
|
+
Crudely parse `src_filename` for `import` statements.
|
|
47
|
+
Return the set of directly imported module names.
|
|
48
|
+
|
|
49
|
+
If `module_name` is not `None`,
|
|
50
|
+
resolve relative imports against it.
|
|
51
|
+
Otherwise, relative import names are returned unresolved.
|
|
52
|
+
|
|
53
|
+
This is a very simple minded source parse.
|
|
54
|
+
|
|
55
|
+
## Function `import_extra(extra_package_name, distinfo)`
|
|
56
|
+
|
|
57
|
+
Try to import the package named `extra_package_name`
|
|
58
|
+
using `importlib.import_module`. Return the imported package.
|
|
59
|
+
|
|
60
|
+
If an `ImportError` is raised,
|
|
61
|
+
riffle through the extras mapping in `distinfo['extras_requires']`
|
|
62
|
+
for the package name, and emit an informative warning
|
|
63
|
+
about the extras which require this package
|
|
64
|
+
and whose use a `pip install` time would bring the package in.
|
|
65
|
+
The original `ImportError` is then reraised.
|
|
66
|
+
|
|
67
|
+
If no extra is found this is presumed to be an error by the caller
|
|
68
|
+
and a `RuntimeError` is raised.
|
|
69
|
+
This function is for internal use as:
|
|
70
|
+
|
|
71
|
+
pkg = import_extra('some_package', DISTINFO)
|
|
72
|
+
|
|
73
|
+
which passes in the source module's `DISTINFO` mapping,
|
|
74
|
+
which I use as the basis for my package distributions.
|
|
75
|
+
|
|
76
|
+
A fuller example from my `cs.timeseries` module's
|
|
77
|
+
`plot` command line mode:
|
|
78
|
+
|
|
79
|
+
def cmd_plot(self, argv):
|
|
80
|
+
\"\"\" Usage: {cmd} datadir days fields...
|
|
81
|
+
\"\"\"
|
|
82
|
+
try:
|
|
83
|
+
import_extra('plotly', DISTINFO)
|
|
84
|
+
except ImportError as e:
|
|
85
|
+
raise GetoptError(
|
|
86
|
+
\"the plotly package is not installed: %s\" % (e,)
|
|
87
|
+
) from e
|
|
88
|
+
|
|
89
|
+
which produces this output:
|
|
90
|
+
|
|
91
|
+
timeseries.py: plot: import_extra('plotly'): package not available; the following extras pull it in: ['plotting']
|
|
92
|
+
timeseries.py: the plotly package is not installed: timeseries.py: plot: import_extra('plotly'): No module named 'plotly'
|
|
93
|
+
|
|
94
|
+
## Function `import_module_from_file(module_name, source_file, sys_path=None)`
|
|
95
|
+
|
|
96
|
+
Import a specific file as a module instance,
|
|
97
|
+
return the module instance.
|
|
98
|
+
|
|
99
|
+
Parameters:
|
|
100
|
+
* `module_name`: the name to assign to the module
|
|
101
|
+
* `source_file`: the source file to load
|
|
102
|
+
* `sys_path`: optional list of paths to set as `sys.path`
|
|
103
|
+
for the duration of this import;
|
|
104
|
+
the default is the current value of `sys.path`
|
|
105
|
+
|
|
106
|
+
Note that this is a \"bare\" import;
|
|
107
|
+
the module instance is not inserted into `sys.modules`.
|
|
108
|
+
|
|
109
|
+
*Warning*: `sys.path` is modified for the duration of this function,
|
|
110
|
+
which may affect multithreaded applications.
|
|
111
|
+
|
|
112
|
+
## Function `import_module_name(module_name, name, path=None, lock=None)`
|
|
113
|
+
|
|
114
|
+
Import `module_name` and return the value of `name` within it.
|
|
115
|
+
|
|
116
|
+
Parameters:
|
|
117
|
+
* `module_name`: the module name to import.
|
|
118
|
+
* `name`: the name within the module whose value is returned;
|
|
119
|
+
if `name` is `None`, return the module itself.
|
|
120
|
+
* `path`: an array of paths to use as sys.path during the import.
|
|
121
|
+
* `lock`: a lock to hold during the import (recommended).
|
|
122
|
+
|
|
123
|
+
## Function `module_attributes(M)`
|
|
124
|
+
|
|
125
|
+
Generator yielding the names and values of attributes from a module
|
|
126
|
+
which were defined in the module.
|
|
127
|
+
|
|
128
|
+
## Function `module_files(M)`
|
|
129
|
+
|
|
130
|
+
Generator yielding `.py` pathnames involved in a module.
|
|
131
|
+
|
|
132
|
+
## Function `module_names(M)`
|
|
133
|
+
|
|
134
|
+
Return a list of the names of attributes from a module which were
|
|
135
|
+
defined in the module.
|
|
136
|
+
|
|
137
|
+
# Release Log
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
*Release 20240630*:
|
|
142
|
+
direct_imports: fix off-by-one resolving leading dot relative import names.
|
|
143
|
+
|
|
144
|
+
*Release 20220606*:
|
|
145
|
+
New import_extra(extra_package_name,distinfo) function to politely try to import a package which is associated with an extra.
|
|
146
|
+
|
|
147
|
+
*Release 20210123*:
|
|
148
|
+
module_attributes: skip values from other modules _if we know the module_ (computed values like tuples have no module and still need to be returned).
|
|
149
|
+
|
|
150
|
+
*Release 20200521*:
|
|
151
|
+
* New import_module_from_file function to import a Python file as a module instance.
|
|
152
|
+
* New direct_imports(src_filename,module_name=None) returning the set of directly imports module names.
|
|
153
|
+
|
|
154
|
+
*Release 20190101*:
|
|
155
|
+
New functions: module_names, module_attributes.
|
|
156
|
+
|
|
157
|
+
*Release 20160918*:
|
|
158
|
+
* New generator function module_files yielding pathnames.
|
|
159
|
+
* import_module_name: accept name=None, just return the module.
|
|
160
|
+
* Add empty \"install_requires\" for DISTINFO completeness.
|
|
161
|
+
|
|
162
|
+
*Release 20150116*:
|
|
163
|
+
Initial PyPI release."""
|
|
164
|
+
content-type = "text/markdown"
|
|
165
|
+
|
|
166
|
+
[build-system]
|
|
167
|
+
build-backend = "setuptools.build_meta"
|
|
168
|
+
requires = [
|
|
169
|
+
"setuptools >= 61.2",
|
|
170
|
+
"trove-classifiers",
|
|
171
|
+
"wheel",
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
[tool.setuptools]
|
|
175
|
+
py-modules = [
|
|
176
|
+
"cs.py.modules",
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
[tool.setuptools.package-dir]
|
|
180
|
+
"" = "lib/python"
|