# Python Packaging
[TOC]
## 87. Section Intro - Python Packaging
- Why packaging?
- 1.Distributing your code
- 2.Non-painful import statements
- 3.Reproducibility
- it work on my machine meme
## 88. PYTHONPATH and imports in Python
```
/home/eric/repos/packaging/
├── my_folder/
│ ├── __init__.py
│ └── my_nested_file.py
├── my_file.py
└── my_other_file.py
```
```python
$PYTHONPATH:/home/eric/repos/packaging python my_folder/my_nested_file.py
```
臨時設定Python模組的搜尋路徑,在不打包的情況下,import寫在某個路徑下的模組
```my_nested_file.py```
```python
from my_otherfile import CONSTANT as CONSTANT2
```
## 89. PYTHONPATH hacking (continued)
- 加入 sys.path
```python=
sys.path.inert(0,"/home/eric/repos")
```
## 90. Our first setup.py file
### 1.Module(模組)
- 單一 .py 檔案
```python
my_module.py
```
- 匯入
```python
import my_module
```
### 2.Package(套件)
- 一個有 __init__.py 的資料夾。一個可被匯入模組集合。
```python
my_package/
├── __init__.py
├── module1.py
└── module2.py
```
- 匯入
```python
from my_package import module1
```
### 3.Sub-package(子套件)
- 套件裡面的另一個套件(有 __init__.py 的資料夾)。
```python
my_package/
├── __init__.py
├── module1.py
└── sub_package/
├── __init__.py
└── module2.py
```
- 匯入
```python
from my_package.sub_package import module2
```
### 4.Distribution Package(發佈套件)
把你的模組/套件"打包成一個可安裝的壓縮包"的版本,可以上傳到PyPI或透過pip安裝。
包含:
setup.py 或 pyproject.toml
原始碼(模組/套件)
README.md、LICENSE 等
|Name | Description | Example |
| -------- | -------- | -------- |
Module | 單一 .py 檔案 | XX_module.py
Package | 有 __init__.py 的資料夾 | XX_package/
Sub-package | Package 裡的子資料夾 | XX_package/sub_package/
Distribution Package | 可被 pip 安裝的壓縮包 | XX_project-0.1.0.tar.gz
- 環境預設有package
- pip
- setuptools
```python
from setuptools import setup, find_packages
setup(
name='my_project', # pip install XXX
version='0.1.0', # 版本
packages=find_packages(), # 自動找所有 __init__.py folder
install_requires=[ # 相依套件
'numpy>=1.20.0',
'pandas',
],
author='XXX',
description='A simple example Python package',
license='MIT',
)
```
- document
```python=
python setup.py --help
```
```python=
python setup.py build sdist
# creating a "source" distribution of a package
# 建立 setup.py 的設定來建立一個 原始碼壓縮包,方便別人安裝或上傳到 PyPI
```
https://docs.python.org/3.10/distutils/sourcedist.html
output:
- *.egg-info/(紀錄metadata)
- build folder
- dist folder -> .tar.gz
- 打包安裝後,你就不需要再手動設 PYTHONPATH 來執行模組
## 91. setuptools docs; find_packages
- 解壓縮
```python
pip install ./dist/packaging-0.0.0.tar.gz
```
- find_packages
- 自動尋找Python套件中所有包含 __init__.py 的資料夾
- https://setuptools.pypa.io/en/latest/userguide/quickstart.html#dependency-management
## 92. pip install --editable
- 傳統安裝pip install . 安裝後改程式碼要重新安裝,用來打包發佈
- pip install -e .(pip install --editable)
## 93. install_requires and reproducibility
- install_requires 相依套件
## 94. Shortcomings of sdist format
- 產生Python套件的分發格式(distribution formats)
| 類型 | sdist (.tar.gz) | wheel (.whl)|
| -------- | -------- | -------- |
格式全名| Source Distribution |Binary Wheel(PEP 427 定義)
內容物| 原始碼 + setup.py |已編譯好的檔案 + metadata
用途 | 提供源碼給 pip 來編譯安裝 | 提供預編譯檔案直接安裝
是否需要編譯器(EX:gcc) | 可能需要 | 不需要
pip 安裝速度 | 慢 | 快
適用對象 | 開發者 | 使用者
## 95. Wheels
- install https://pypi.org/project/wheel/
```python
pip install wheel
```
- 用setup.py建立.whl格式
```python
python setup.py bdist_wheel
```
## 96. pyproject.toml origin story; Build dependencies explained
```python=
setup_requires=[
"wheel"
]
```
```toml=
# pyproject.toml
[build-system]
requires = ["wheel"]
```
## 97/98/99. Escaping config file hell; setup.cfg/Removing setup.cfg thanks to PEP 621/Removing setup.py; PEP 517; Build backends
|setup.py |pyproject.toml |setup.cfg |
| -------- | -------- | -------- |
python code | toml格式設定檔 | ini格式設定檔
傳統,定義專案的安裝、依賴等 | 用來統一所有的配置項 | setuptools 用來配置和定義版本、依賴等的文件
- 1. ```setup.py``` => ```pyproject.toml```
```python=
from setuptools import setup
setup(
name='my_project',
version='0.1',
packages=['my_package'],
install_requires=[
'numpy',
'flask'
],
extras_require={
'dev': ['pytest', 'sphinx'],
'docs': ['sphinx']
}
)
```
- 2.```setup.cfg``` => ```pyproject.toml```
```ini=
[metadata]
name = my_project
version = 0.1
[options]
packages = find:
install_requires =
numpy
flask
[options.extras_require]
dev =
pytest
sphinx
docs =
sphinx
```
- output:
```TOML=
[project]
name = "my_project"
version = "0.1"
dependencies = [
"numpy",
"flask"
]
[project.optional-dependencies]
dev = ["pytest", "sphinx"]
docs = ["sphinx"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
```
## 100/101. Data files - part 1; example use case for including data/ Part 2; MANIFEST.in and build-backend-specific alternatives
- MANIFEST.in 配置文件
- 讓 setuptools、distutils 或其他打包工具,哪些文件需要被包含/排除進 package。
```
recursive-include data *.json #recursive-include
```
## 102. Dependency management; Dependency graphs and locking dependencies
## 103. Dependency graphs
- 列出目前環境已安裝的套件,顯示依賴關係的工具(安裝[pipdeptree](https://pypi.org/project/pipdeptree/))
```
pip install pipdeptree
```
- 查看
```
pipdeptree
```
```
xgboost==2.1.0
├── numpy [required: Any, installed: 2.0.0]
└── scipy [required: Any, installed: 1.14.0]
└── numpy [required: >=1.23.5,<2.3, installed: 2.0.0]
```
- 反向查詢(--reverse),哪些package需要這個package作為依賴。
```
pipdeptree --reverse --packages numpy
```
```
numpy==2.0.0
├── contourpy==1.3.1 [requires: numpy>=1.23]
│ └── matplotlib==3.10.1 [requires: contourpy>=1.0.1]
├── matplotlib==3.10.1 [requires: numpy>=1.23]
├── pandas==2.2.2 [requires: numpy>=1.26.0]
│ └── streamlit==1.43.2 [requires: pandas>=1.4.0,<3]
├── pydeck==0.9.1 [requires: numpy>=1.16.4]
│ └── streamlit==1.43.2 [requires: pydeck>=0.8.0b4,<1]
├── scikit-learn==1.5.1 [requires: numpy>=1.19.5]
├── scipy==1.14.0 [requires: numpy>=1.23.5,<2.3]
│ ├── scikit-learn==1.5.1 [requires: scipy>=1.6.0]
│ └── xgboost==2.1.0 [requires: scipy]
├── streamlit==1.43.2 [requires: numpy>=1.23,<3]
└── xgboost==2.1.0 [requires: numpy]
```
- graphviz 畫圖
- Constraint tree(約束樹):
安裝每個套件時,會帶來它需要的其他套件(dependencies)和對這些套件版本的要求(constraints)
```
A 需要 B >=1.0, <2.0
C 需要 B >=1.5, <1.6
```
## 104. Constraint trees and "dependency hell"
- Dependency Hell(依賴地獄):
當不同套件對同一依賴要求版本互相衝突,導致根本無法同時安裝/或者要調整才能安裝
## 105. Constraint trees are unstable across package versions
- 避免
- 1.虛擬環境(Virtual Environment)
- 2.鎖定套件版本(用 requirements.txt 或 pyproject.toml + poetry.lock)
## 106. Optional dependencies AKA "extras"
- 選擇性的額外依賴(optional dependencies)
- 某些功能,才需要的額外套件, PEP 621, 用 [project.optional-dependencies] 在 pyproject.toml 裡
```
[project.optional-dependencies]
dev = [
"pytest>=6.0",
"black>=22.3",
"mypy>=1.0",
]
viz = [
"matplotlib>=3.5",
"seaborn>=0.12",
]
aws = [
"boto3>=1.26"
]
```
```
pip install .[dev]
```
## 107. Shopping for dependencies with the Snyk Python package index
- 套件健檢 [Snyk](https://snyk.io/advisor/)
- package health
```
pip install snyk
```
登入
[snyk document](https://docs.snyk.io/)
```
snyk auth
```
掃描你的專案中所有的```requirements.txt```、```pyproject.toml```等檔案
```
snyk test --all-projects
```