在剛開始開發時,對於套件管理還有系統路徑的不熟悉
可能安裝不同套件到不同的路徑下,會出現明明有install但是import時找不到module的問題
或是IDE偵測套件的路徑也不同,造成開發上常常會遇到找不到套件的狀況
即使安裝套件到相同的位置,可是不同的專案會需要用到不同的套件,可能不同專案,需要不同版本的同一個套件
pip freeze成requirements.txt時,也沒有辦法分辨哪些套件是這個專案的
在install套件時,除了主要套件,可能其他相依性套件也會被一起安裝
你也不會知道會不會有複數主套件共用同一個相依性套件的情形,如果為了專案整潔而刪除到共用的套件,可能會導致專案無法使用~
這些都是單獨使用pip時可能會遇到的問題
而poetry則是能處理以上痛點的開發工具
接下來就進到poetry的完整介紹,會分成幾點:
poetry具有以下特點:
為什麼使用poetry?跟virtualenv與pipenv有區別嗎?
而poetry當然也不是完全沒有爭議出現,在這篇中最下方的留言區也有人提到poetry對於deprecated觀念的擔憂,所以可以自己評估這些事情對專案開發時的利弊,決定最終想使用哪一套工具
有三種安裝方式:
最不建議的就是安裝到專案中的虛擬環境,有幾個原因:
在官方文檔也有提到
Poetry should always be installed in a dedicated virtual environment to isolate it from the rest of your system.
而使用pip的話,需要先自己創建虛擬環境,activate之後再進行安裝,最後再設定PATH
步驟比較繁瑣,而curl安裝也是需要最後設定PATH,相較之下pipx算是最便捷的方式了,因其會自動建立虛擬環境外,也會幫忙把路徑加到PATH中,算是非常方便~
因此我個人建議使用pipx安裝或是curl安裝
使用curl安裝:
# linux, macOS, WSL(Windows Subsystem for Linux)
curl -sSL https://install.python-poetry.org | python3 -
# windows
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
1. vim ~/.zshrc (or .bashrc)
2. 輸入export PATH=$PATH:$HOME/.local/bin
3. source ~/.zshrc 或是重啟terminal
poetry --version
output: Poetry (version 1.7.1)
使用pipx安裝:
brew install pipx
pipx ensurepath
# pipx ensurepath用於確認執行檔有沒有加到PATH中
pipx install poetry
installed package poetry 1.7.1, installed using Python 3.12.1
These apps are now globally available
- poetry
⚠️ Note: '/Users/nb050/.local/bin' is not on your PATH environment variable.
These apps will not be globally accessible until your PATH is updated. Run
`pipx ensurepath` to automatically add it, or manually modify your PATH in
your shell's config file (i.e. ~/.bashrc).
done! ✨ 🌟 ✨
pipx ensurepath
pipx ensurepath
Success! Added /Users/nb050/.local/bin to the PATH environment variable.
Consider adding shell completions for pipx. Run 'pipx completions' for
instructions.
You will need to open a new terminal or re-login for the PATH changes to take
effect.
Otherwise pipx is ready to go! ✨ 🌟 ✨
在開始之前,可以先輸入poetry config --list
指令去看當下的設定
cache-dir = "/Users/nb050/Library/Caches/pypoetry"
experimental.system-git-client = false
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.options.always-copy = false
virtualenvs.options.no-pip = false
virtualenvs.options.no-setuptools = false
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/nb050/Library/Caches/pypoetry/virtualenvs
virtualenvs.prefer-active-python = false
virtualenvs.prompt = "{project_name}-py{python_version}"
warnings.export = true
其中virtualenvs.in-projectv
需要改成true,這樣才會在專案內生成虛擬環境
poetry config virtualenvs.in-project true
使用poetry指令之前,必須要有pyproject.toml
檔,而要有pyproject.toml
檔,我們需要進行初始化的步驟
poetry能快速建立一個python project,並且有包含一些預設檔案
poetry new project_name
.
├── README.md
├── airflow_project
│ └── __init__.py
├── pyproject.toml
└── tests
└── __init__.py
poetry new my-folder --name my-package
此時我們可以看一下pyproject.toml
的內容
[tool.poetry]
name = "airflow-project"
version = "0.1.0"
description = ""
authors = ["your_eamil <your_eamil@xxx.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
在tool.poetry
中有這個專案的一些基本資訊,如作者、專案名稱等等
而poetry其中一個功能就是能區分開發時依賴(tool.poetry.dev-dependencies
)與運行時依賴(tool.poetry.dependencies
)
以及可以指定build專案的資訊(build-system
)
tool.poetry.dependencies
這邊可以注意到poetry會偵測系統的python版本,所以使用pyenv來控制python版本時可以注意一下
那也可以用另一種作法:
先建立專案資料夾以及相關的包,接著在專案資料夾內輸入
poetry init
poetry init幫助建立pyproject.toml
檔案,輸入後系統會依序問你想要設置的環境與套件
既然專案內的poetry配置檔都好了,我們就來介紹poetry的相關指令吧~
既然我們都設定好pyproject.toml
了,我們就把我們需要用的套件安裝下來吧~
poetry install
Creating virtualenv airflow-project in /Users/nb050/airflow_project/.venv
Updating dependencies
Resolving dependencies... (0.1s)
Writing lock file
Installing the current project: airflow-project (0.1.0)
可以看到他做了兩件事:
poetry.lock
的話,會直接拿來解析,反之則建立poetry.lock
檔案poetry.lock
是根據pyproject.toml
的內容生成,但是如果單獨修改pyproject.toml
而沒有做後續指令,poetry.lock
是不會同步更新的。
內容如下:
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
package = []
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "34e39677d8527182346093002688d17a5d2fc204b9eb3e094b2e6ac519028228"
可以看到有儲存python版本之外,最重要的是有儲存content-hash
,他是對該套件的內容進行哈希運算,也就是取決於套件的版本、關係與源碼檔案等等,所以今天即使版本相同,但是內容有被竄改,content-hash
也會被改變
如果我們需要在專案內新增套件,可以使用以下指令
poetry add package_name
如果要指定不同版本,有以下表達方式
# 允許 >=2.0.5, <3.0.0 之間的版本
poetry add package_name@^2.0.5
# 允許 >=2.0.5, <2.1.0 之間的版本
poetry add package_name@~2.0.5
# 允許大於 >=2.0.5 版本,且沒有最新限制
poetry add "package_name>=2.0.5"
# 只允許 2.0.5 版本
poetry add package_name==2.0.5
例如:
poetry add pymysql
Using version ^1.1.0 for pymysql
Updating dependencies
Resolving dependencies... (0.5s)
Package operations: 1 install, 0 updates, 0 removals
• Installing pymysql (1.1.0)
Writing lock file
可以看到畫面整潔度比起pip install整潔許多XD
poetry update
# 或指定特定套件
poetry update package_name
poetry會根據pyproject.toml
內的套件或是根據指令的參數來更新特定套件,並且他強大之處在於能在更新套件時一起解析與更新他的依賴套件
poetry show
pymysql 1.1.0 Pure Python MySQL Driver
在使用時需要注意:
poetry show --all
poetry.lock
的內容來列出來,所以如果toml與lock檔不同步,或是你使用額外非poetry的方式安裝套件,就不會顯示。假如是單純toml與lock檔不同步,可以直接用poetry lock --no-update
指令來同步poetry show package_name
來顯示指定套件的依賴狀況poetry show apache-airflow
name : apache-airflow
version : 2.8.0
description : Programmatically author, schedule and monitor data pipelines
dependencies
- alembic >=1.6.3,<2.0
- apache-airflow-providers-common-sql *
- apache-airflow-providers-ftp *
- apache-airflow-providers-http *
- apache-airflow-providers-imap *
- apache-airflow-providers-sqlite *
- argcomplete >=1.10
- asgiref *
poetry show --tree
來看整個樹狀圖apache-airflow 2.8.0 Programmatically author, schedule and monitor data pipelines
├── alembic >=1.6.3,<2.0
│ ├── importlib-metadata *
│ │ └── zipp >=0.5
│ ├── importlib-resources *
│ │ └── zipp >=3.1.0 (circular dependency aborted here)
│ ├── mako *
│ │ └── markupsafe >=0.9.2
│ ├── sqlalchemy >=1.3.0
│ │ └── greenlet !=0.4.17
│ └── typing-extensions >=4
├── apache-airflow-providers-common-sql *
│ ├── apache-airflow >=2.6.0 (circular dependency aborted here)
│ └── sqlparse >=0.4.2
├── apache-airflow-providers-ftp *
│ └── apache-airflow >=2.6.0 (circular dependency aborted here)
├── apache-airflow-providers-http *
│ ├── aiohttp *
│ │ ├── aiosignal >=1.1.2
│ │ │ └── frozenlist >=1.1.0
│ │ ├── async-timeout >=4.0,<5.0
poetry remove package_name
Updating dependencies
Resolving dependencies... (0.1s)
Package operations: 0 installs, 0 updates, 12 removals, 1 skipped
• Removing certifi (2023.11.17)
• Removing charset-normalizer (3.3.2)
• Removing click (8.1.7)
• Removing idna (3.6)
• Removing itsdangerous (2.1.2)
• Removing jinja2 (3.1.2)
• Removing markupsafe (2.1.3)
• Removing multidict (6.0.4)
• Removing packaging (23.2)
• Removing sniffio (1.3.0)
• Removing urllib3 (2.1.0)
• Removing werkzeug (2.3.8)
• Installing pymysql (1.1.0): Skipped for the following reason: Already installed
在使用時要注意:
--dry run
來預跑看看poetry export -f requirements.txt --output requirements.txt
需要注意:
--format(-f)
:輸出的格式,預設為requirements.txt。目前只支援constraints.txt跟requirements.txttool.poetry.dependencies
內有的套件--require-hashes
來達到更安全的檢查模式,詳情可以看這篇
poetry shell
會進入到poetry創建的虛擬環境,要退出時用exit而不是deactivate
poetry run
後面接的命就相當於在shell中執行一樣
poetry check
檢查pyproject.toml
與poetry.lock
是否一致
poetry lock
會將pyproject.toml
中的依賴項鎖定,如果只是想同步lock檔,可以添加—-no-update
poetry env
後面可以接指令,主要是與虛擬環境互動
poetry env use /full/path/to/python
: 也可以簡寫成python3.x,只要在$PATH有指定的版本,poetry就會用該版本的python來建立虛擬環境poetry env remove /full/path/to/python
: 後面的寫法跟上面相同,就是看$PATH。主要作用為刪除特定虛擬環境poetry env info —-path
, poetry env list
: 列出虛擬環境的路徑以及列出虛擬環境資料夾名稱,但是因為需要當前目錄或父目錄中有pyproject.toml
檔,不然找不到在最佳 Python 套件管理器——Poetry 完全入門指南中有介紹了有關現有專案改用poetry、在別台主機上重現專案的poetry環境、移除並重建虛擬環境等等,內容很清楚,可以直接拜讀
如果專案是用poetry啟動的,那要如何與系統其他軟體做串接呢?
其實很單純,因為虛擬環境中的套件只是獨立存儲在其他的路徑下,因此在調用時注意路徑就好
這邊用nginx
、uwsgi
與django
做示範:
poetry new test_django_uwsgi
pyproject.toml
檔[tool.poetry]
name = "test-django-uwsgi"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.9"
django = "^3.2.15"
uwsgi = "^2.0.23"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
poetry config virtualenvs.in-project true
沒有的話,需要先設定,不然就需要下poetry env remove python
來把先前建立的虛擬環境刪除poetry install
asgiref 3.7.2 ASGI specs, helper code, and adapters
django 3.2.23 A high-level Python Web framework that encou...
pytz 2023.3.post1 World timezone definitions, modern and histo...
sqlparse 0.4.4 A non-validating SQL parser.
typing-extensions 4.9.0 Backported and Experimental Type Hints for P...
uwsgi 2.0.23 The uWSGI server
poetry run django-admin startproject my_app
也可以到shell中操作,不能直接打django-admin,不然就不是用虛擬環境的套件了
settings.py
中的ALLOWED_HOSTS
ALLOWED_HOSTS = ["your host"]
.
├── manage.py
├── my_app
│ ├── asgi.py
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-39.pyc
│ │ ├── settings.cpython-39.pyc
│ │ └── urls.cpython-39.pyc
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── uwsgi.ini
[uwsgi]
socket = 127.0.0.1:7999 #找一個沒被佔用的port
chdir = /path/to/test_django_uwsgi/my_app
wsgi-file=my_app/wsgi.py
processes = 4
threads=2
master = true
vacuum = true
buffer-size=65536
pidfile=uwsgi.pid
uid=123 # 在terminal中輸入id找出當前用戶uid與gid
gid=123
/etc/systemd/system
,建立service檔cd /etc/systemd/system
vim my_app.service
[Service]
ExecStart=/path/to/test_django_uwsgi/.venv/bin/uwsgi --ini /path/to/test_django_uwsgi/my_app/uwsgi.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target
這邊的ExecStart前面的uwsgi就是要輸入虛擬環境下的uwsgi,後面就是接ini檔的位置
# 轉成root
systemctl daemon-reload
systemctl start my_app.service
journalctl -u test_poetry.service -f
有出現Started my_app.service.
就成功了~
vim /etc/nginx/nginx.conf
# 確認裡面有沒有底下這行
include /etc/nginx/conf.d/*.conf;
# 或是自己建立其他conf檔路徑
# 在自己建立的conf檔中輸入
server {
listen 81;
server_name test_django;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:7999;
}
}
nginx -t
# 都沒問題後
systemctl reload nginx
firewall-cmd --zone=public --add-port=81/tcp --permanent
firewall-cmd --reload
最後就能在瀏覽器看到預設的首頁了
在製作docker image時使用poetry來建立環境時可能會遇到幾個問題:
不過假如今天在開發環境都是使用poetry開發的話,poetry匯出的requirements.txt可能使用pip安裝時會有相容性的問題,且現在借助docker的multi-stage builds,可以處理image過大的問題
multi-stage builds
也就是在Dockerfile
中使用多次的FROM,每一次的FROM都可以用不同的基底image建構,並且可以將這一階段建構的一些內容轉到下一個階段,在最終的階段中篩選掉不需要保留的內容。實際的操作方法可以參考以下的示範流程
以下內容參考自:Blazing fast Python Docker builds with Poetry
我們先用最簡單的方式來建立image檔來看檔案會多大:
當前目錄結構
.
├── Dockerfile
└── pyproject.toml
Dockefile:
FROM python:3.11-buster
RUN apt-get update \
&& apt-get install -y pipx \
&& pipx ensurepath \
&& apt-get install -y python3-venv \
&& pipx install poetry==1.5.0 \
&& pipx ensurepath
COPY pyproject.toml .
ENV PATH="/root/.local/bin:${PATH}"
WORKDIR .
RUN poetry install
pyproject.toml
[tool.poetry]
name = "airflow-project"
version = "0.1.0"
description = ""
authors = ["your_email <your_email@xxx.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = ">=3.8,<3.12"
apache-airflow = { version = "^2.8", python = ">=3.8,<3.12" }
pymysql = "^1.1.0"
apache-airflow-providers-docker = "^3.0.0"
apache-airflow-providers-celery = "^3.0.0"
flower = "^1.0.0"
importlib-metadata = "^4.13.0"
pydantic = "^2.3.0"
pandas = "^1.1.5"
loguru = "^0.5.3"
celery = { version = "*", extras = ["redis"] }
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
這樣做出來的大小有1.72GB這麼大,因此我們需要將它慢慢瘦身
但在此之前,先提一些做這個基本image的小細節
pyproject.toml
中包含tool.poetry.dev-dependencies
時,可以在poetry install
的指令改成poetry install --without dev
設置相關參數
依照個人需求來設置環境變數
POETRY_VIRTUALENVS_IN_PROJECT=true
:設置為true時,poetry會在專案的根目錄底下的.venv
中建立虛擬環境POETRY_VIRTUALENVS_CREATE=true
:如果還沒建立虛擬環境時會建立一個虛擬環境POETRY_CACHE_DIR
:設置cache的資料夾位置額外建立虛擬環境都是避免影響到系統的python或是poetry,讓這三者能彼此獨立
清除poetry cache
因為poetry會保存安裝套件的快取,但是我們基本上不會再需要這些檔案,所以可以將他們清除
此時的Dockerfile如下
FROM python:3.11-buster
RUN apt-get update \
&& apt-get install -y pipx \
&& pipx ensurepath \
&& apt-get install -y python3-venv \
&& pipx install poetry==1.5.0 \
&& pipx ensurepath
COPY pyproject.toml .
ENV PATH="/root/.local/bin:${PATH}" \
POETRY_VIRTUALENVS_IN_PROJECT=true \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache
WORKDIR .
RUN poetry install \
&& rm -rf $POETRY_CACHE_DIR
使用multi-stage builds
修改Dockfile
FROM python:3.11-buster as builder
RUN apt-get update \
&& apt-get install -y pipx \
&& pipx ensurepath \
&& apt-get install -y python3-venv \
&& pipx install poetry==1.5.0 \
&& pipx ensurepath
COPY pyproject.toml .
ENV PATH="/root/.local/bin:${PATH}" \
POETRY_VIRTUALENVS_IN_PROJECT=true \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache
WORKDIR .
RUN poetry install \
&& rm -rf $POETRY_CACHE_DIR
# the runtime image, used to just run the code provided its virtual environment
FROM python:3.11-slim-buster as runtime
ENV VIRTUAL_ENV=./.venv \
PATH="./.venv/bin:$PATH"
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
以上可以注意幾點:
最後我們的成品大小為432MB,為原本的1/4左右,算是蠻成功的一次瘦身~
當然在這邊如果需要再去增進image製作的速度等更細節的優化,可以去拜讀原本的文章,這邊就不再特別說明了~
以上就是有關poetry的一些使用場景,我沒有使用太多其他套件管理相關工具的經驗,所以這篇與其說是要推坑poetry,反而比較像是要給那些已經想使用,但是可能還沒有相關知識的人去看。並且這對我來說,也是方便我自己在使用時,不用一直去翻相關的文檔,直接透過這一篇來進行部署或是操作等等,希望能幫助到想使用的人~
參考文章: