概要
久しぶりにPythonライブラリを開発、リリースしたのでその感想と備忘録を残していきます。
お前だれ?
ブラジルでデータサイエンティスト/機械学習エンジニアやってます。この記事を書き始めてわかったけど、思ってた以上に日本語長文の書き方忘れてて焦ってます。ところどころ変な日本語にご勘弁。
プライベートで開発してたサービスが一段落したので練習がてらPythonライブラリを開発、リリースまでしてみたので、その感想と備忘録を残していきます。
何作ったの?
今回作ったのはデータ管理のためのライブラリ。練習用とはいえ、想定ユーザーはデータサイエンティスト。
データサイエンティストがタスクやプロジェクトで複数箇所からデータを取ってきて、クリーニング、バリデーション、タスクのためのデータセット作成を行うときにそれらのデータを手軽に管理できるライブラリを作ってみました。
インストールは
pip install dcraft
使い方
以下の例はデータをRaw Layerにセーブ、ロードする。dcraftではデータをRaw layer、Trusted layer、Refined layerにセーブして管理します。それぞれの役割はこんな感じ。
- Raw Layer: ソースから取ってきたデータそのまんま
- Trusted Layer: クリーニングとバリデーション後のデータ
- Refined Layer: 個別のタスク、目的に応じたデータ
これらのデータを現在のバージョンだとローカルかGCP上にmetadataと共に保存していく感じ。
import os
import pandas as pd
from dcraft import create_raw, LocalDataRepository, LocalMetadataRepository, read_layer_data
data = pd.DataFrame({"a": [1,2], "b": [None, 4]})
raw_layer_data = create_raw(
data,
"fake-project",
"Shuhei Kishi",
"This is fake project",
{"version": "0.0.1"}
)
CURRENT_DIR = os.getcwd()
DATA_DIR_PATH = os.path.join(CURRENT_DIR, "data")
METADATA_DIR_PATH = os.path.join(CURRENT_DIR, "metadata")
data_repository = LocalDataRepository(DATA_DIR_PATH)
metadata_repository = LocalMetadataRepository(DATA_DIR_PATH)
raw_layer_data.save("parquet", data_repository, metadata_repository)
# loaded_raw_layer_data = read_layer_data("id-from-metadata", data_repository, metadata_repository)
やったこと
コーディング
基本的にはデータのストア、保存、ロードを管理するだけのライブラリなので内部アーキテクチャはシンプルに進めていった。interfaceとdomainに分けて開発していく感じ。最初はクリーンアーキテクチャのレイヤー構造にならって進めていこうと思っていたけど、ライブラリ開発におけるuse case layer
の勘所がわからなかったので今回は不採用。
現時点でローカルとGCPのリソースに保存可能にしてるので、テストはユニットテストとインテグレーションテストの2つ。
シンプルに始めようってことで常にあるbranchはmain branchのみ。機能追加やバグ修正はそこからbranch切ってmerge.
CI/CD
個人開発の味方、Github Actions、でテストとデプロイは自動化。大体の必要な機能はすでにプラグインが存在してるので、ググって使っていく感じで。
ドキュメンテーション
sphinxを使用。main branchにmergeしたらRead the Docsにデプロイ。
案外きつかったこと
想定ユーザーに合わせたインターフェース
社内ツールはそこそこ作ってきているので気軽な気持ちで開発し始めたけど、OSSとなると想定ユーザーが使いやすく感じるのかという部分が気になって、結局、開発過程で結構な頻度で名前やパスを変更することになりました。
『こっちのほうが綺麗だけどユーザー視点で面倒か?』みたいな思考と常に格闘する感じ。
どこまでやる?
普段はビジネスが絡んだ文脈で開発してるので、ビジネス視点で必要な機能とクオリティを定義して開発プロセスを作ってきたけど、OSS開発だとちょっと途方に暮れたというのが実際のところ。
最初のバージョンではライブラリのアイデンティティを維持するための機能とクオリティがあれば、他の部分は継続的に足して行けば良いわけだけど、結局その通りにはできなかった感じで、結局いくつかの機能を足してデプロイ後にあるべき物を作ってなかったことに気づいて『うーんっ』ってなってる。
具体的にはローカルへのデータ保存で最初のバージョンは多分十分だったのにGCPリソースの機能つけちゃった。でもって、データをオブジェクトにストアする段階でするべきバリデーションを忘れてた。
Sphinx
普段はドキュメンテーションはConfluence上でマニュアルでやってたので、そういえばSphinxを使うのは初めてだった。そこそこ長くデータサイエンティストやってるけど、使ったことなかったなーって気づいてちょっと驚いた。
ライブラリ開発のための最小限のフロー
ライブラリの形をなすために
Pythonではsetup.py
を書いてroot
ディレクトリに置いておけばライブラリになる。
今回はこんな感じ。
from setuptools import find_packages, setup
deps = ["pandas", "pyarrow"]
gcp_deps = ["google-cloud-storage>=2.6.0", "google-cloud-bigquery", "pyarrow"]
test_requires = ["pytest"]
setup(
name="dcraft",
version="0.1.0",
packages=find_packages(exclude=["tests"]),
install_requires=deps,
test_requires=test_requires,
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
extras_require={"gcp": gcp_deps, "test": test_requires},
author="Shuhei Kishi",
)
正直このsetup.py
だと雑なので後で色々弄くる。
Pypi
Githubからコードをcloneしてローカルからインストールすることもできるけれど、Pypiにアップロードすると誰でも簡単にpip install
コマンドだけでインストールができる。
以下のリンクにアクセスしてユーザー登録。テスト環境もあるのでそっちも登録推奨。
https://pypi.org/
CI/CD
とりあえずテストとデプロイは自動化したい。JenkinsでもGithub Actionsでも良いから使うべき。
個人的には、個人開発でGithubを使ってるならGithub Actionsが楽でおすすめ。
プロジェクトのroot
ディレクトリに.github/workflows
ディレクトリを作って、ymlファイルを置くだけで良し。
今回はこんな感じ。ごちゃごちゃ書いてあるけど、テストして、ビルドしてデプロイしてるだけ。
name: Publish to PyPI
on: [push]
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[test]
- name: Test with pytest
run: |
python -m pytest test
build:
name: Build distribution 📦
runs-on: ubuntu-latest
needs:
- test
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install pypa/build
run: >-
python3 -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v3
with:
name: python-package-distributions
path: dist/
publish-to-pypi:
name: >-
Publish Python 🐍 distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags/')
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/dcraft
permissions:
id-token: write
steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
path: dist/
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
ドキュメンテーション
今回はSphinxを使用。以下のコマンドでテンプレートを作成して弄っていく。
pip install sphinx
sphinx-quickstart
docstringからの作成部分は以下のコマンドで。
sphinx-apidoc -f -o ./docs/source/code_generated .
感想
仕事でもプライベートでも毎日そこそこの開発は行って来ているので気軽に始めたけれど、やってみると気にすることが多くて思っていたよりもしんどかったというのが正直なところ。
せっかくなので継続的に開発を続けていきたい。
おまけ
Github上では使用してる言語の割合をグラフで出してるんだけど、このプロジェクトを始めるまでは全てのPythonプロジェクトをプライベートリポで進めてた。データサイエンティスト/機械学習エンジニアとして働いてるのに使用言語にPythonはない状態が続いていた。この開発でPythonが使用言語に出てきたのでGithub上の情報がデータサイエンティストっぽくなった。