Skip to content

Instantly share code, notes, and snippets.

@joonhwan
Created February 9, 2021 01:47
Show Gist options
  • Save joonhwan/6a0e19d2fbeeff852f2ddce8ce8180d4 to your computer and use it in GitHub Desktop.
Save joonhwan/6a0e19d2fbeeff852f2ddce8ce8180d4 to your computer and use it in GitHub Desktop.
Python Package 만들기 / 2021년2월

python 프로젝트 구성하기(python package 만들기)

pyproject.toml 작성

빌드시스템(디폴트는 setuptools 이지만, poetry, fitt 등 다른것도 있음) 구성과 관련해 필요한 패키지, 설정등이 들어감.

[build-system]
requires = [
    "setuptools>=42",
    "wheel"
]
build-backend = "setuptools.build_meta"

pyproject.toml 파일이 없는 경우에는 classic setuptools build system 을 쓴다?고 한다

아래의 내용을 통해..

setup.cfg (정적 메타데이터 설정)

setuptools 를 위한 프로젝트의 메타데이터(이름, 버젼, 작성자, 홈페이지 정보... 등등)을 기록.

이전에는 setup.py 에 주저리 주저리 기록했지만, 이제는 정적인 정보들은 setup.cfg 파일에 기록.

[metadata]
name = my_packaged_app
version = 0.0.1
url = https://github.com/pypa/sampleproject
author = Example Author
author_email = author@example.com
classifiers =
    Programming Language :: Python :: 3
    License :: OSI Approved :: MIT License
    Operating System :: OS Independent
description = A small example package
long_description = file: README.md
long_description_content_type = text/markdown

[options]
python_requires = >=3.7

자세히 보면 예전에 setuptool.py에 기록하던 내용들 중 동적으로 판단할 필요가 없는(사람이 수동으로 기록해야 하는) 것들이 setup.cfg 로 옮겨간 거 같다.

사실, 현재의 예제에는 setup.cfg 파일내에 별다른 packages 설정이 없다. 예전 setup.py 에서는 종종

import setuptools
# .. 중략
setuptools.setup(
    # 중략
     packages=setuptools.find_packages(),
     # 중략
)

이런식으로 root 밑에 있는 패키지를 자동으로 검색하곤 했는데, 그럼 setup.cfg에서는 이걸 어떻게 설정하지?

setup.cfg 에 설정할 수 있는 항목들에 대해서는 Configuring setup() using setup.cfg files 문서 참고.

setup.cfgoptions.packages 라는 설정항목이 존재하며,

  • find:
  • find_namespace:
  • list-comma

방식으로 설정할 수 있다. 이를테면...

# ...

[options]
package = find:
# ...

차럼 명시 할 수 있다. 기본값이 find: 이며, 이 경우, 자동으로 setuptools.find_packages() 를 통해 찾아지는 패키지들(root경로 아래에서 찾은 파이썬 패키지(__init__.py 파일이 있는 디렉토리)들)이 포함된다. Configuring setup() using setup.cfg files 문서에는 사용자가 특별하게 구성한 소스코드 디렉토리 구성에 대한 설정법이 있다.

├── src
│   └── mypackage
│       ├── __init__.py
│       └── mod1.py
├── setup.py
└── setup.cfg

위와 같은 구조의 소스코드 레이아웃을 가지는 경우, 아래 처럼 하면 src 아래에서 패키지를 검색한다(즉 mypackage).

# This example contains just the necessary options for a src-layout, set up
# the rest of the file as described above.
[options]
package_dir=
    =src
packages=find:

[options.packages.find]
where=src

setup.py (동적 메타데이터 설정 + setuptools 실행)

setup.cfg 를 사용하기 때문에, 원래는

import setuptools

with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setuptools.setup(
    name="my_packaged_app", # Replace with your own username
    version="0.0.1",
    author="Example Author",
    author_email="author@example.com",
    description="A small example package",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://github.com/pypa/my_packaged_app",
    packages=setuptools.find_packages(),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires='>=3.7',
)

처럼 길게 써야 할 것이

import setuptools

setuptools.setup()

처럼 간단히 줄어든다....

README.md 작성

setup.cfg 에서

[metadata]
# ... 
long_description = file: README.md
long_description_content_type = text/markdown
# ...

처럼 작성된 부분은 이 프로젝트에 대한 상세설명으로 README.md 파일을 사용하게 하며, 포맷이 text/markdown 임을 지정했다. 이 파일을 작성하면 pypi 패키지 설명화면에 표시된다.

License 파일

python package 는 License 파일이 있어야 한다(파일명.LICENSE)

https://choosealicense.com/ 에서 license 파일에 사용할 수 있는 몇가지 구문을 찾을 수 있지만, 잘 모르겠고 아무나 써도 되면 MIT License로 간다.

MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

배포파일(Distribution Archive) 생성하기

이를 위해서는 build 패키지가 설치되어 있어야 한다. 아래의 명령

python -m pip install --upgrade build

을 실행하면 최신버젼의 build 도구가 현재의 python환경(가상환경쓰고 있지? 꼭 써야대)에 설치된다.

이제 python -m build --help 하면 build 패키지가 module 모드로 실행되면서, 사용할 수 있는 도움말이 나오는데.... 그냥 소스코드 최상위 디렉토리에서 python -m build 하면 dist 디렉토리(없다면 생성된다)에 2개의 파일이 생성된다.

dist
├── my_packaged_app-0.0.1-py3-none-any.whl
└── my_packaged_app-0.0.1.tar.gz

위에서

  • my_packaged_app-0.0.1.tar.gz : sdist 즉, source archive 파일이며, 압축을 풀면 \my_packaged_app 디렉토리 있는 소스코드들이 들어있다.
  • my_packaged_app-0.0.1-py3-none-any.whl : bdist_wheel 즉, built distribution 이며, python의 표준패키지 포맷이다

참고로 위의 파일 말고 my_packaged_app.egg-info 라는 디렉토리도 생기는데, whl 파일 이전에 쓰이던 egg 포맷과 관련이 있는거 같은데, 항상 생긴다. 귀찮게....

배포파일(Distribution Archive)를 pypi에 올리기.

  • PyPI 계정만들기
  • API Token 받기

진짜 PyPI에는 올리기전에, 연습을 하기 위해 Test PyPi 를 사용해보자. 이 사이트는 올린것을 주기적으로 지우기 때문에 연습을 위해 부담없이 사용할 수 있다.

사이트에서 계정을 만들고 로그인한다. 그런다음 email 확인을 통해 계정을 활성화하고, Account Settings - Test PyPI 페이지의 아래쪽에 있는 API Token 에서 Add API Token 버튼을 누르고, 적당한 이름의 토큰을 하나 만든다.

그리고 나면 딱 한번만 내용을 볼 수 있는 토큰값이 표시된다. 이걸 잘 기록해두어야 한다.

이제 사용자 home 경로의 .pypirc(리눅스에서는 ~/.pypirc) 파일에 아래와 같이 기록하되 아래의 내용중 __token__ 이라고 되어 있는 부분에는 사용자의 username을 기록해 야 한다. 그리고, password 부분에는 아까 받은 토큰값을 적는다(pypi- 로 시작하도록 되어있어야 한다)

[testpypi]
  username = __token__
  password = pypi-{받은 토큰값}

실제로

[testpypi]
  username = joonhwan
  password = pypi-AgENdGVzdC5weXBpLm9yZwIkOGQ0YWRmNzQtMzI0YS00MjEwLTg5YmItZjczNDI5NDdkY2ZiAAIleyJwZXJtaXNzaW9ucyI6ICJ1c2VyIiwgInZlcnNpb24iOiAxfQAABiDGbbHy4fp9KzfBww4MaAqaIozPEdjcbbLdOhuAnp_xoA

처럼 되어야 한다.

이제 Test-PyPi 로 올리기 위해, 필요한 twine 을 설치한다.

python -m pip install --upgrade twine

그런다음 설치확인을 위해 python -m twine --help 해 본다.

이제 업로드를 하기 위해,

python3 -m twine upload --repository testpypi dist/* 명령을 수행하면, .... 오류가 난다.

> python -m twine upload --repository testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
Uploading my_packaged_app-0.0.1-py3-none-any.whl
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 14.0k/14.0k [00:01<00:00, 9.09kB/s]
NOTE: Try --verbose to see response content.
HTTPError: 403 Forbidden from https://test.pypi.org/legacy/
Invalid or non-existent authentication information. See https://test.pypi.org/help/#invalid-auth for more information.

위 링크에 들어가보니, 그냥 username/password 로도 올릴 수 있나보다. 😅

~/.pypirc 을 삭제하고, 다시 실행

 python -m twine upload --repository testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
/home/jhlee/prj/study/python/packaging_tutorial/.venv/lib/python3.7/site-packages/secretstorage/dhcrypto.py:16: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead
  from cryptography.utils import int_from_bytes
/home/jhlee/prj/study/python/packaging_tutorial/.venv/lib/python3.7/site-packages/secretstorage/util.py:25: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead
  from cryptography.utils import int_from_bytes
Enter your username: (여기서 Test PyPI 사용자명 입력)
ll the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
  warnings.warn(str(exc))
Enter your password: (여기서 Test PyPI의 접속 암호 입력)
Uploading my_packaged_app-0.0.1-py3-none-any.whl
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 14.0k/14.0k [00:02<00:00, 5.43kB/s]
Uploading my_packaged_app-0.0.1.tar.gz
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 13.4k/13.4k [00:00<00:00, 14.4kB/s]
View at:
https://test.pypi.org/project/my-packaged-app/0.0.1/

하니까 올라간다....

주의 2021년 2월 기준, 패키지 생성 튜토리얼을 따라한 후 발견한 문제들.

setup.pypackages = find_packages() 가 없으면 잘 안되는 경우가 있다.

dist 폴더에 생성된 파일의 압축을 풀면 우리가 만든 소스코드(즉, my_packaged_app*.py 파일(들))가 없는 거 같다. 먼가 이상하지만, setup.py 의 내용을 아래처럼 하고 python -m build 를 하면, 소스 코드가 들어 있는걸 확인할 수 있다.

import setuptools

setuptools.setup(
    packages=setuptools.find_packages(),
)

python -m build 를 하면, pip 버젼이 안맞는다는 경고문구.

현재 python 가상환경에서 python -m pip install wheel 한 다음, python -m build --no-isolation 을 수행하면, 현재 가상환경의 패키지들을 사용해 build 를 수행한다. python -m build 를 하면, build 할 때 마다, build 패키지가 새로운 isolation 가상환경을 만들고 거기에서 build를 수행한다(이때 자꾸 pip 버젼이 outdated 되었다는 경고가 나오더라)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment