発端
doclingを使って、マークダウンファイルへの変換の精度などを調べています。
生成AIへの入力データとしてマークダウンファイルが注目されているため、この分野がホットになっているためか、doclingも最近頻繁に更新されています。
そのため、マークダウンファイルの生成コマンドだけでなく、その時に用いたライブラリーのバージョンも記録しておきたくなりました。
Python製ビルドツールであるdoitは、実行するアクションにpythonコードも指定できます。その機能を活用して、ライブラリーの更新やライブラリーのバージョンのログへの記録などをdoit経由で行えるようにしました。
- doitの使用方法は以下を参照ください。
ソース
# import libraries
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from importlib.metadata import PackageNotFoundError, version
# Global variables for configuration
_logfile = Path("doit_log.md")
# for default
_pip_libs = [ # 更新を記録すべきライブラリーを全て書いておく
"docling",
]
# for default
_pdffiles = [ # 生成元のpdfを全て書いておく
"hogehoge.pdf",
"fugafuga.pdf",
]
# configuration table
conf = {
"logfile": _logfile,
"pdffiles": _pdffiles,
"pip_libs": _pip_libs,
}
# utility functions
# ## for configuration
# ### setup(): set up configration
def setup(**kwargs):
""" サブタスクで対象とするファイルのリストなどを設定する
"""
global conf
conf.update(kwargs)
# for logging
# ### write_log()
def write_log(logfile, datefile=None, **kwargs):
"""Markdown形式のログを追記する。
Args:
logfile:
ログファイル。
datefile:
指定した場合、そのファイルの最終更新日時をログのDateとする。
Noneの場合は現在日時を使用する。
**kwargs:
ログに記録する項目。
dictの場合はネストしたMarkdownリストとして出力する。
"""
if datefile is None:
date = datetime.now()
else:
date = last_updated_day(datefile)
lines = [f"# Date: {date:%Y-%m-%d %H:%M}"]
for key, value in kwargs.items():
if isinstance(value, dict):
lines.append(f"- {key}:")
lines.extend(
f" - {k}: {v}"
for k, v in value.items()
)
else:
lines.append(f"- {key}: {value}")
lines.append("")
log = "\n".join(lines) + "\n"
if True:
print(log)
if logfile:
with Path(logfile).open("a", encoding="utf-8") as f:
f.write(log)
# ### get_version()
def get_version(name):
"""インストール済みライブラリーのバージョンを返す。"""
try:
return version(name)
except PackageNotFoundError:
return "Not installed"
# ### log_library_versions()
def log_library_versions(*libraries):
"""指定したライブラリーの現在のバージョンをログに記録する。"""
global conf
versions = {
name: get_version(name)
for name in libraries
}
write_log(
conf["logfile"],
Libraries=versions,
)
# ## task_log_library_versions()
def task_log_library_versions():
"""pip_libsに登録されたライブラリーの現在のバージョンをログに記録する。"""
global conf
return {
"actions": [
(log_library_versions, conf["pip_libs"])
],
}
# ## for python library update
# ### last_updated_day()
def last_updated_day(datefile):
return datetime.fromtimestamp(Path(datefile).stat().st_mtime)
# ### pip_upgrade()
def pip_upgrade(name):
"""指定したライブラリーをupgradeし、変更時のみログを残す。"""
global conf
before = get_version(name)
subprocess.run(
[
sys.executable,
"-m",
"pip",
"install",
"--upgrade",
name,
],
check=True,
)
after = get_version(name)
if before != after:
write_log(
conf["logfile"],
Task="pip upgrade",
Library=name,
Version_before=before,
Version_after=after,
)
# ### task_pip_upgrade()
def task_pip_upgrade():
"""指定ライブラリーをpipでupgradeする。"""
global conf
for name in conf["pip_libs"]:
yield {
"name": name,
"actions": [(pip_upgrade, [name])],
"uptodate": [False],
}
# ## for conversion to markdown file
# ### pdf2md_with_docling_action()
def pdf2md_with_docling_action(src: str, dst: str):
"""PDFをDoclingでMarkdownへ変換し、成功時にログを記録する。"""
# Doclingは出力先をディレクトリで指定するため、一旦通常名で生成
global conf
dst = Path(dst)
subprocess.run(
[
"docling",
src,
"--to", "md",
"--image-export-mode", "referenced",
"--output", str(dst.parent),
],
check=True,
)
# Doclingが生成したファイル名を目的の名前へ変更
generated = dst.parent / f"{Path(src).stem}.md"
generated.replace(dst)
write_log(
conf["logfile"],
Source=src,
Target=str(dst),
Action="created with Docling",
)
# ### task_pdf2md_with_docling()
def task_pdf2md_with_docling():
"""PDFをDoclingでMarkdownへ変換する。"""
global conf
for pdf in conf["pdffiles"]:
src = Path(pdf)
dst = src.with_name(f"{src.stem}(docling).md")
yield {
"name": pdf,
"actions": [
(pdf2md_with_docling_action, [str(src), str(dst)])
],
"file_dep": [str(src)],
"targets": [str(dst)],
"clean": True,
}
使い方
現状のライブラリーのバージョンの記録
doit log_library_versions
マークダウンファイルへの変換
doit pdf2md_with_docling:hogehoge.pdf
ライブラリーのアップデート
doit pip_upgrade:docling