[英]How do I get the current 'package' name? (setup.py)
How do I get the current topmost package, ie, the name defined in setup.py?如何获取当前最顶层的包,即setup.py 中定义的名称?
Here is my tree
:这是我的
tree
:
.
|-- README.md
|-- the_project_name_for_this_pkg
| |-- __init__.py
| |-- __main__.py
| |-- _config
| | `-- foo.conf
| |-- _data
| | `-- logging.yml
| `-- tests
| |-- __init__.py
| `-- test_foo.py <--- # executing from here
|-- requirements.txt
`-- setup.py
4 directories, 9 files
The only solution I've gotten to work so far is this:到目前为止,我已经开始工作的唯一解决方案是:
import os
import sys
os.path.basename(sys.path[1])
But this is obviously a bad solution.但这显然是一个糟糕的解决方案。 Other solutions like having a
__name__
in my uppermost __init__.py
file and using ast.parse
to read in the relevant section of setup.py
also seems cumbersome.其他解决方案,比如在我最上面的
__init__.py
文件中有一个__name__
并使用ast.parse
读取setup.py
的相关部分也似乎很麻烦。
Other solutions I've tried—by calling them within a unittest.TestCase
inheriting class
in my tests
python [sub]package—include checking sys.modules[__name__]
, inspect.getmodule
& inspect.stack
, as well as the answers to these questions:我尝试过的其他解决方案 - 通过在我的
tests
python [sub] 包中的unittest.TestCase
继承class
中调用它们 - 包括检查sys.modules[__name__]
, inspect.getmodule
& inspect.stack
,以及这些的答案问题:
BTW: In case you were wondering why I want the package name… it's so I can run things like:顺便说一句:如果你想知道为什么我想要包名......这样我就可以运行这样的东西:
import pkg_resources
version = pkg_resources.require('the_project_name_for_this_pkg')[0].version
data_file = path.join(resource_filename('the_project_name_for_this_pkg', '__init__.py'),
'_config', 'data_file.txt')
Not entirely sure what the larger goal is, but maybe you could be interested in reading about importlib.resources
as well asimportlib.metadata
.不完全确定更大的目标是什么,但也许您可能对阅读
importlib.resources
和importlib.metadata
。
Something like the following:类似于以下内容:
import importlib.metadata
import importlib.resources
version = importlib.metadata.version('SomeProject')
data = importlib.resources.files('top_level_package.sub_package').joinpath('file.txt').read_text()
And more generally, it is near impossible (or not worth the amount of work) to 100% reliably detect the name of the project ( SomeProject
) from within the code.更一般地说,从代码中 100% 可靠地检测项目的名称 (
SomeProject
) 几乎是不可能的(或不值得工作量)。 It is easier to just hard-code it.仅对其进行硬编码更容易。
Nevertheless here are some techniques, and ideas to retrieve the name of the project from one of its modules:不过,这里有一些技术和想法,可以从其中一个模块中检索项目名称:
Update :更新:
I believe some function like the following should return the name of the installed distribution containing the current file:我相信像下面这样的函数应该返回包含当前文件的已安装发行版的名称:
import pathlib
import importlib_metadata
def get_project_name():
for dist in importlib_metadata.distributions():
try:
relative = pathlib.Path(__file__).relative_to(dist.locate_file(''))
except ValueError:
pass
else:
if relative in dist.files:
return dist.metadata['Name']
return None
Update (February 2021):更新(2021 年 2 月):
Looks like this could become easier thanks to the newly added packages_distributions()
function in importlib_metadata
:由于
importlib_metadata
新添加的packages_distributions()
函数,看起来这会变得更容易:
A solution I've been working on:我一直在研究的解决方案:
from os import listdir, path
from contextlib import suppress
import ast
def get_first_setup_py(cur_dir):
if 'setup.py' in listdir(cur_dir):
return path.join(cur_dir, 'setup.py')
prev_dir = cur_dir
cur_dir = path.realpath(path.dirname(cur_dir))
if prev_dir == cur_dir:
raise StopIteration()
return get_first_setup_py(cur_dir)
setup_py_file_name = get_first_setup_py(path.dirname(__file__))
First pass:第一关:
def get_from_setup_py(setup_file): # mostly https://stackoverflow.com/a/47463422
import importlib.util
spec = importlib.util.spec_from_file_location('setup', setup_file)
setup = importlib.util.module_from_spec(spec)
spec.loader.exec_module(setup)
# And now access it
print(setup)
That option did work.该选项确实有效。 So I returned to the
ast
solution I referenced in the question, and got this second pass to work:所以我回到了我在问题中引用的
ast
解决方案,并让第二次通过:
def parse_package_name_from_setup_py(setup_py_file_name):
with open(setup_py_file_name, 'rt') as f:
parsed_setup_py = ast.parse(f.read(), 'setup.py')
# Assumes you have an `if __name__ == '__main__'` block:
main_body = next(sym for sym in parsed_setup_py.body[::-1]
if isinstance(sym, ast.If)).body
setup_call = next(sym.value
for sym in main_body[::-1]
if isinstance(sym, ast.Expr)
and isinstance(sym.value, ast.Call)
and sym.value.func.id in frozenset(('setup',
'distutils.core.setup',
'setuptools.setup')))
package_name = next(keyword
for keyword in setup_call.keywords
if keyword.arg == 'name'
and isinstance(keyword.value, ast.Name))
# Return the raw string if it is one
if isinstance(package_name.value, ast.Str):
return package_name.value.s
# Otherwise it's a variable defined in the `if __name__ == '__main__'` block:
elif isinstance(package_name.value, ast.Name):
return next(sym.value.s
for sym in main_body
if isinstance(sym, ast.Assign)
and isinstance(sym.value, ast.Str)
and any(target.id == package_name.value.id
for target in sym.targets)
)
else:
raise NotImplemented('Package name extraction only built for raw strings & '
'assigment in the same scope that setup() is called')
Third pass (works for both installed and development versions):第三遍(适用于安装版和开发版):
# Originally from https://stackoverflow.com/a/56032725;
# but made more concise and added support whence source
class App(object):
def get_app_name(self) -> str:
# Iterate through all installed packages and try to find one
# that has the app's file in it
app_def_path = inspect.getfile(self.__class__)
with suppress(FileNotFoundError):
return next(
(dist.project_name
for dist in pkg_resources.working_set
if any(app_def_path == path.normpath(path.join(dist.location, r[0]))
for r in csv.reader(dist.get_metadata_lines('RECORD')))),
None) or parse_package_name_from_setup_py(
get_first_setup_py(path.dirname(__file__)))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.