返回文章列表

Python專案封裝容器化最佳實踐

本文探討 Python 專案封裝與容器化的最佳實踐,涵蓋 setup.py 的撰寫、依賴管理、Docker 映像構建以及軟體架構中的抽象層設計。透過 pip-tools 精確管理依賴版本,並結合 Docker

Web 開發 系統設計

Python 專案的封裝和佈署流程中,確保程式碼的可重複性、可維護性以及跨平台一致性至關重要。本文介紹的最佳實踐涵蓋了從 setup.py 的撰寫、依賴管理到 Docker 映像構建的完整流程。利用 setuptools 函式庫簡化封裝流程,並使用 pip-tools 鎖定依賴版本,確保開發和生產環境的一致性。此外,Docker 容器技術的應用更進一步提升了應用程式的可攜性,使其在不同環境中都能穩定執行。同時,文章也強調了軟體架構中抽象層設計的重要性,透過介面卡模式和依賴注入等設計原則,有效降低程式碼耦合度,提升程式碼的可維護性和可擴充套件性。

封裝Python專案的最佳實踐

在開發Python專案時,正確地封裝和分發是非常重要的。這不僅確保了專案的可重複性和可維護性,還使得其他開發者能夠輕鬆地安裝和使用你的專案。

使用setup.py進行專案封裝

setup.py是Python專案封裝的核心檔案,它包含了專案的後設資料和依賴關係。透過使用setuptools函式庫,我們可以輕鬆地建立一個setup.py檔案。

from setuptools import find_packages, setup

with open("README.rst", "r") as longdesc:
    long_description = longdesc.read()

install_requires = ["sanic>=20,<21"]

setup(
    name="web",
    description="Library with helpers for the web-related functions",
    long_description=long_description,
    author="Dev team",
    version="0.1.0",
    packages=find_packages(where="src/"),
    package_dir={"": "src"},
    install_requires=install_requires,
)

內容解密:

  1. find_packages(where='src/'):自動發現src/目錄下的所有套件,避免將測試檔案或無關檔案納入專案中。
  2. package_dir={'': 'src'}:指定套件所在的目錄,這裡是src/
  3. install_requires=install_requires:定義專案所需的依賴套件及其版本範圍。
  4. long_description=long_description:從README.rst檔案中讀取長描述,提供給PyPI使用。

管理依賴關係

為了確保專案的可重複性,我們需要管理好依賴關係。這包括了指定依賴套件的版本範圍和使用工具來編譯requirements.txt檔案。

使用pip-tools編譯requirements.txt

pip install pip-tools
pip-compile > requirements.txt

內容解密:

  1. pip install pip-tools:安裝pip-tools,這是一個用於管理依賴關係的工具。
  2. pip-compile > requirements.txt:根據setup.py中的依賴關係編譯出requirements.txt檔案,列出所有依賴套件的確切版本。

Docker化你的應用

為了使應用程式在不同環境中平滑執行,使用Docker是一個好方法。Docker可以確保你的應用程式在任何地方都以相同的方式執行。

建立Docker映像

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "app.py"]

內容解密:

  1. FROM python:3.9-slim:使用官方的Python 3.9映像作為基礎映像。
  2. COPY requirements.txt .:將requirements.txt檔案複製到容器中。
  3. RUN pip install --no-cache-dir -r requirements.txt:安裝依賴套件。
  4. COPY . .:將當前目錄下的所有檔案複製到容器中。
  5. CMD ["python", "app.py"]:指定容器啟動時執行的命令。

管理依賴與版本控制的最佳實踐

在軟體開發過程中,正確管理依賴是確保專案穩定性和可維護性的關鍵。本文將探討如何使用工具和技術來有效管理依賴,並確保專案的版本控制。

使用 pip-compile 管理依賴

為了確保專案的依賴被正確管理,我們可以使用 pip-compile 工具從 setup.py 檔案生成 requirements.txt 檔案。這樣可以確保每次構建 Docker 映像時,都使用相同的依賴版本,從而實作版本控制的確定性。

pip-compile setup.py

這個命令會生成一個 requirements.txt 檔案,包含了所有依賴的精確版本。我們應該在 Dockerfile 中使用這個檔案來安裝依賴,以確保每次構建都是一致的。

內容解密:

  1. pip-compile 工具用於根據 setup.py 中的依賴定義生成 requirements.txt
  2. 使用 requirements.txt 安裝依賴可確保 Docker 構建的一致性。
  3. requirements.txt 檔案置於版本控制之下,以便追蹤依賴的變更。

管理依賴的其他考量

預設情況下,pip 會從公共 PyPI 倉函式庫安裝依賴。然而,這種做法有一些限制和問題,例如依賴於外部服務的可用性,以及無法發布內部套件。

內容解密:

  1. 使用內部依賴伺服器可以解決對外部服務的依賴問題。
  2. 內部套件應釋出到公司內部的 PyPI 伺服器,以保護智慧財產權。
  3. 需要定期更新內部倉函式庫,以確保獲得最新的安全修補和功能。

自動化依賴更新

為了保持依賴的最新狀態,我們可以組態工具自動傳送 Pull Request 以更新依賴版本。同時,也應該組態自動安全檢查,以確保依賴的安全性。

內容解密:

  1. 組態工具自動更新 requirements.txt,以便在有新版本時通知開發者。
  2. 自動化安全檢查有助於及時發現和修復安全漏洞。
  3. 持續整合(CI)流程應包括自動化測試,以確保更新依賴後專案仍然穩定。

版本管理與穩定性

在追求最新版本的同時,也需要考慮穩定性。軟體版本管理需要平衡穩定性和最新功能之間的取捨。

內容解密:

  1. 軟體版本應遵循一定的規則,如 PEP-440,以確保版本號具有明確的含義。
  2. 更新依賴版本時,應考慮是否需要發布新版本的 artifact。
  3. 正確的版本管理有助於維護專案的穩定性和可預測性。

容器化技術在Python專案中的應用

在軟體開發領域,容器化技術已經成為一種主流的佈署方式。相較於傳統的虛擬機器,容器提供了更輕量級、更快速的佈署方案。本章節將重點介紹Docker容器技術在Python專案中的應用。

為什麼需要容器化?

在過去,Python專案的佈署常常因為環境差異而導致各種問題。由於Python是一種解釋型語言,程式碼需要在目標環境中由Python直譯器執行。這意味著開發者需要確保生產環境中的Python版本與開發環境一致。此外,依賴套件的管理也是一大挑戰,尤其是當某些套件依賴於特定的C函式庫或作業系統層級的元件時。

容器化技術,如Docker,提供瞭解決這些問題的方案。透過將Python應用程式封裝成Docker映像,我們可以確保應用程式在不同環境中的一致性,從而簡化開發、測試和佈署流程。

Docker容器基礎

Docker容器需要一個映像來執行,而這個映像是根據其他基礎映像建立的。我們建立的映像也可以作為其他容器的基礎映像。在某些情況下,如果我們的應用程式有多個容器分享相同的基礎組態,我們會希望建立一個基礎映像來安裝特定的套件及其依賴項。

使用Docker的好處

  1. 可攜性:容器化技術使得應用程式能夠在不同環境中平滑執行,無需擔心環境差異帶來的問題。
  2. 易於開發和測試:透過容器化,我們可以輕鬆地在本地環境中重現生產環境,從而簡化測試流程。
  3. 維護和擴充套件性:容器化技術符合「關注點分離」(Separation of Concerns, SoC)的設計原則。每個容器封裝了一組獨立的功能,使得維護和擴充套件變得更加容易。

例項探討:食品配送服務的狀態追蹤API

假設我們正在開發一個食品配送服務,其中包含一個用於追蹤訂單狀態的REST API服務。這個服務需要從資料函式庫中取得訂單資訊,並以JSON格式傳回給客戶端。

服務設計

我們的服務主要關注兩個方面:

  1. 資料存取:從資料函式庫中取得訂單資訊。
  2. 資料呈現:將訂單資訊以JSON格式傳回給客戶端。

為了保持服務的可維護性和可擴充套件性,我們將這兩個關注點抽象成獨立的Python套件。主應用程式負責核心邏輯,並使用這兩個套件來完成資料存取和呈現的工作,如圖10.1所示。

建立Docker容器

在下一節中,我們將介紹如何為Python專案建立Docker容器。具體步驟包括建立Dockerfile、構建Docker映像以及執行Docker容器等。

Dockerfile範例

# 使用官方Python映像作為基礎
FROM python:3.9-slim

# 設定工作目錄
WORKDIR /app

# 複製需求檔案
COPY requirements.txt .

# 安裝依賴套件
RUN pip install --no-cache-dir -r requirements.txt

# 複製應用程式碼
COPY . .

# 暴露服務埠
EXPOSE 8000

# 執行命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

內容解密:

  1. FROM python:3.9-slim:使用官方的Python 3.9映像作為基礎映像,slim版本是為了減小映像大小。
  2. WORKDIR /app:設定容器內的工作目錄為/app
  3. COPY requirements.txt .:將本地的requirements.txt檔案複製到容器內的工作目錄。
  4. RUN pip install --no-cache-dir -r requirements.txt:安裝requirements.txt中指定的依賴套件。
  5. COPY . .:將當前目錄下的所有檔案複製到容器內的工作目錄。
  6. EXPOSE 8000:暴露容器的8000埠,以便外部存取。
  7. CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]:設定容器的預設執行命令,使用uvicorn啟動FastAPI應用程式。

軟體架構中的抽象層設計

在現代軟體開發中,良好的架構設計是確保系統可維護性、可擴充套件性和可讀性的關鍵。本文將探討如何透過抽象層設計來實作這些目標,並以一個具體的範例來說明其實作方式。

抽象層的重要性

在軟體開發中,直接與底層技術(如資料函式庫或網頁框架)互動可能會導致程式碼與這些技術緊密耦合。這種緊耦合會使程式碼難以維護和適應變化。透過引入抽象層,可以有效地隔離核心業務邏輯與外部技術細節,從而提高程式碼的靈活性和可維護性。

範例:配送服務應用程式

考慮一個名為「Web 服務」的應用程式,它使用了兩個 Python 套件,其中一個用於連線資料函式庫。這種設計展示瞭如何透過抽象化元件來實作有效的隔離。

程式碼結構

首先,我們定義了一些業務規則的類別(Domain Models),這些類別代表了不同的訂單狀態,例如 DispatchedOrderOrderInTransitOrderDelivered。這些類別是純粹的業務物件,不依賴於任何特定的技術實作。

from typing import Union

class DispatchedOrder:
    """An order that was just created and notified to start its delivery."""
    status = "dispatched"
    def __init__(self, when):
        self._when = when
    def message(self) -> dict:
        return {
            "status": self.status,
            "msg": "Order was dispatched on {0}".format(self._when.isoformat()),
        }

class OrderInTransit:
    """An order that is currently being sent to the customer."""
    status = "in transit"
    def __init__(self, current_location):
        self._current_location = current_location
    def message(self) -> dict:
        return {
            "status": self.status,
            "msg": "The order is in progress (current location: {})".format(self._current_location),
        }

class OrderDelivered:
    """An order that was already delivered to the customer."""
    status = "delivered"
    def __init__(self, delivered_at):
        self._delivered_at = delivered_at
    def message(self) -> dict:
        return {
            "status": self.status,
            "msg": "Order delivered on {0}".format(self._delivered_at.isoformat()),
        }

class DeliveryOrder:
    def __init__(self, delivery_id: str, status: Union[DispatchedOrder, OrderInTransit, OrderDelivered]):
        self._delivery_id = delivery_id
        self._status = status
    def message(self) -> dict:
        return {"id": self._delivery_id, **self._status.message()}

應用程式中的使用

接下來,我們展示瞭如何在應用程式中使用這些物件。應用程式依賴於 storageweb 套件,但這些套件的內部實作細節對應用程式是透明的。

from storage import DBClient, DeliveryStatusQuery, OrderNotFoundError
from web import NotFound, View, app, register_route

class DeliveryView(View):
    async def _get(self, request, delivery_id: int):
        dsq = DeliveryStatusQuery(int(delivery_id), await DBClient())
        try:
            result = await dsq.get()
        except OrderNotFoundError as e:
            raise NotFound(str(e)) from e
        return result.message()

register_route(DeliveryView, "/status/<delivery_id:int>")

抽象層的優勢

透過上述設計,我們實作了以下優勢:

  1. 宣告式程式設計:我們的程式碼宣告了我們想要解決的問題,而不是如何解決它。這使得程式碼更加直觀和易於理解。
  2. 隔離變化:核心業務邏輯與外部技術細節(如資料函式庫和網頁框架)隔離,使得系統更容易適應變化。
  3. 提高可維護性:由於程式碼與特定技術實作解耦,因此維護和修改變得更加容易。

容器化 Python 應用程式:技術深度解析

在現代軟體開發中,容器化技術已成為佈署和管理應用程式的主流選擇。本文將探討如何將 Python 應用程式容器化,特別是使用 Docker 技術的實務經驗與技術細節。

應用程式架構與介面卡模式

在開始容器化之前,瞭解應用程式的架構至關重要。該應用程式依賴於外部 API,例如 DeliveryStatusQuery,其實作細節被抽象化。這種設計使得更換底層實作變得相對容易,只需確保新的物件實作 get() 方法即可傳回 DeliveryOrder

介面卡模式的應用

透過觀察應用程式的結構,可以推斷出 webstorage 等套件內部使用了介面卡設計模式。這種模式允許外部實作被適配到應用程式定義的 API,從而實作了依賴倒置原則。值得注意的是,View 類別繼承自 web 套件中的 View 類別,這是一種透過繼承實作的介面卡模式。

# DeliveryStatusQuery 的簡化範例
class DeliveryStatusQuery:
    def __init__(self, delivery_id, db_client):
        self.delivery_id = delivery_id
        self.db_client = db_client

    async def get(self):
        # 實作細節被抽象化
        pass

內容解密:

  1. DeliveryStatusQuery 類別封裝了查詢運單狀態的邏輯。
  2. 建構子接受 delivery_iddb_client,展現了依賴注入的設計原則。
  3. get() 方法被設計為非同步,適合用於非同步 I/O 操作。

容器化 Python 應用程式

容器化應用程式涉及建立一個 Docker 容器,該容器包含執行應用程式所需的環境和依賴項。

目錄結構與依賴管理

典型的 Python 專案目錄結構如下:

├── Dockerfile
├── libs
│   ├── storage
│   └── web
├── Makefile
├── setup.py
└── statusweb
    ├── __init__.py
    └── service.py

在這個結構中,setup.py 檔案負責定義應用程式的依賴項。

# setup.py 範例
from setuptools import find_packages, setup

with open("README.rst", "r") as longdesc:
    long_description = longdesc.read()

install_requires = ["web==0.1.0", "storage==0.1.0"]

setup(
    name="delistatus",
    description="Check the status of a delivery order",
    long_description=long_description,
    author="Dev team",
    version="0.1.0",
    packages=find_packages(),
    install_requires=install_requires,
    entry_points={
        "console_scripts": [
            "status-service = statusweb.service:main",
        ],
    },
)

內容解密:

  1. setup.py 使用 setuptools 來定義專案的中繼資料和依賴項。
  2. install_requires 列出了專案所需的依賴項,包括自定義的 webstorage 套件。
  3. entry_points 定義了一個可執行的指令 status-service,它對映到 statusweb.service 模組中的 main 函式。