패키지 버전을 setup.py 및 패키지와 공유하는 올바른 방법은 무엇입니까?
와 distutils
, setuptools
등의 패키지 버전이 지정됩니다 setup.py
:
# file: setup.py
...
setup(
name='foobar',
version='1.0.0',
# other attributes
)
패키지 내에서 동일한 버전 번호에 액세스하고 싶습니다.
>>> import foobar
>>> foobar.__version__
'1.0.0'
__version__ = '1.0.0'
패키지의 __init__.py에 추가 할 수 있지만 패키지에 대한 단순화 된 인터페이스를 생성하기 위해 패키지에 추가 가져 오기를 포함하고 싶습니다.
# file: __init__.py
from foobar import foo
from foobar.bar import Bar
__version__ = '1.0.0'
과
# file: setup.py
from foobar import __version__
...
setup(
name='foobar',
version=__version__,
# other attributes
)
그러나 이러한 추가 가져 오기로 인해 foobar
아직 설치되지 않은 다른 패키지를 가져 오는 경우 설치 가 실패 할 수 있습니다 . 패키지 버전을 setup.py 및 패키지와 공유하는 올바른 방법은 무엇입니까?
setup.py
에서만 버전을 설정 하고을 사용하여 자신의 버전을 읽고 pkg_resources
효과적으로 setuptools
메타 데이터를 쿼리합니다 .
파일: setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
파일: __init__.py
from pkg_resources import get_distribution
__version__ = get_distribution('foobar').version
이를 설치하지 않고 실행할 수있는 모든 경우에이 작업을 수행하려면 DistributionNotFound
배포 위치와 테스트를 수행 하십시오.
from pkg_resources import get_distribution, DistributionNotFound
import os.path
try:
_dist = get_distribution('foobar')
# Normalize case for Windows systems
dist_loc = os.path.normcase(_dist.location)
here = os.path.normcase(__file__)
if not here.startswith(os.path.join(dist_loc, 'foobar')):
# not installed, but there is another version that *is*
raise DistributionNotFound
except DistributionNotFound:
__version__ = 'Please install this project with setup.py'
else:
__version__ = _dist.version
나는 이것에 대한 정식 답변이 있다고 생각하지 않지만 내 방법 (직접 복사하거나 다른 여러 곳에서 보았던 것에서 약간 조정)은 다음과 같습니다.
폴더 계층 (관련 파일 만 해당) :
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
main_package/_version.py
:
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
main_package/__init__.py
:
"""Something nice and descriptive."""
from main_package.some_module import some_function_or_class
# ... etc.
from main_package._version import __version__
__all__ = (
some_function_or_class,
# ... etc.
)
setup.py
:
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
... 그것은 죄처럼 못 생겼지 만 작동합니다. 그리고 나는 그것이 있다면 더 나은 방법을 알기를 기대하는 사람들이 배포 한 패키지에서 그것을 보았습니다.
다음에 대한 @ stefano-m의 철학 에 동의합니다 .
갖는 버전 소스의 = "XYZ"를하고 setup.py 내에서 구문 분석하는 것은, 확실히 이럴 올바른 솔루션입니다. 런타임 매직에 의존하는 것보다 훨씬 낫습니다.
그리고이 대답은 @ zero-piraeus의 대답 에서 파생되었습니다 . 요점은 "setup.py에서 가져 오기를 사용하지 말고 대신 파일에서 버전을 읽습니다"입니다.
정규식을 사용하여 구문 분석을 수행 __version__
하므로 전용 파일의 마지막 줄이 될 필요가 없습니다. 사실, 나는 여전히 __version__
내 프로젝트의 __init__.py
.
폴더 계층 (관련 파일 만 해당) :
package_root/
|- main_package/
| `- __init__.py
`- setup.py
main_package/__init__.py
:
# You can have other dependency if you really need to
from main_package.some_module import some_function_or_class
# Define your version number in the way you mother told you,
# which is so straightforward that even your grandma will understand.
__version__ = "1.2.3"
__all__ = (
some_function_or_class,
# ... etc.
)
setup.py
:
from setuptools import setup
import re, io
__version__ = re.search(
r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', # It excludes inline comment too
io.open('main_package/__init__.py', encoding='utf_8_sig').read()
).group(1)
# The beautiful part is, I don't even need to check exceptions here.
# If something messes up, let the build process fail noisy, BEFORE my release!
setup(
version=__version__,
# ... etc.
)
... 아직 이상적이지는 않지만 작동합니다.
그런데이 시점에서 다음과 같은 방식으로 새 장난감을 테스트 할 수 있습니다.
python setup.py --version
1.2.3
PS: This official Python packaging document (and its mirror) describes more options. Its first option is also using regex. (Depends on the exact regex you use, it may or may not handle quotation marks inside version string. Generally not a big issue though.)
PPS: The fix in ADAL Python is now backported into this answer.
Put __version__
in your_pkg/__init__.py
, and parse in setup.py
using ast
:
import ast
import importlib.util
from pkg_resources import safe_name
PKG_DIR = 'my_pkg'
def find_version():
"""Return value of __version__.
Reference: https://stackoverflow.com/a/42269185/
"""
file_path = importlib.util.find_spec(PKG_DIR).origin
with open(file_path) as file_obj:
root_node = ast.parse(file_obj.read())
for node in ast.walk(root_node):
if isinstance(node, ast.Assign):
if len(node.targets) == 1 and node.targets[0].id == "__version__":
return node.value.s
raise RuntimeError("Unable to find version string.")
setup(name=safe_name(PKG_DIR),
version=find_version(),
packages=[PKG_DIR],
...
)
If using Python < 3.4, note that importlib.util.find_spec
is not available. Moreover, any backport of importlib
of course cannot be relied upon to be available to setup.py
. In this case, use:
import os
file_path = os.path.join(os.path.dirname(__file__), PKG_DIR, '__init__.py')
Based on the accepted answer and comments, this is what I ended up doing:
file: setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
file: __init__.py
from pkg_resources import get_distribution, DistributionNotFound
__project__ = 'foobar'
__version__ = None # required for initial installation
try:
__version__ = get_distribution(__project__).version
except DistributionNotFound:
VERSION = __project__ + '-' + '(local)'
else:
VERSION = __project__ + '-' + __version__
from foobar import foo
from foobar.bar import Bar
Explanation:
__project__
is the name of the project to install which may be different than the name of the packageVERSION
is what I display in my command-line interfaces when--version
is requestedthe additional imports (for the simplified package interface) only occur if the project has actually been installed
The accepted answer requires that the package has been installed. In my case, I needed to extract the installation params (including __version__
) from the source setup.py
. I found a direct and simple solution while looking through the tests of the setuptools package. Looking for more info on the _setup_stop_after
attribute lead me to an old mailing list post which mentioned distutils.core.run_setup
, which lead me to the actual docs needed. After all that, here's the simple solution:
file setup.py
:
from setuptools import setup
setup(name='funniest',
version='0.1',
description='The funniest joke in the world',
url='http://github.com/storborg/funniest',
author='Flying Circus',
author_email='flyingcircus@example.com',
license='MIT',
packages=['funniest'],
zip_safe=False)
file extract.py
:
from distutils.core import run_setup
dist = run_setup('./setup.py', stop_after='init')
dist.get_version()
'IT박스' 카테고리의 다른 글
Rails 콘솔을 사용하여 테이블에서 열을 제거하는 방법 (0) | 2020.12.08 |
---|---|
채널을 읽지 않고 닫혔는지 여부를 확인하는 방법은 무엇입니까? (0) | 2020.12.08 |
Julia에서 @printf가 함수 대신 매크로 인 이유는 무엇입니까? (0) | 2020.12.08 |
Android 지원 중단 아파치 모듈 (HttpClient, HttpResponse 등) (0) | 2020.12.08 |
Node.js 기반 서버 대 Apache HTTP 서버와 같은 것 (0) | 2020.12.08 |