0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

使用ライブラリーのバージョンを含めてファイルの更新情報を記録しよう

0
Posted at

発端

doclingを使って、マークダウンファイルへの変換の精度などを調べています。
生成AIへの入力データとしてマークダウンファイルが注目されているため、この分野がホットになっているためか、doclingも最近頻繁に更新されています。

そのため、マークダウンファイルの生成コマンドだけでなく、その時に用いたライブラリーのバージョンも記録しておきたくなりました。

Python製ビルドツールであるdoitは、実行するアクションにpythonコードも指定できます。その機能を活用して、ライブラリーの更新やライブラリーのバージョンのログへの記録などを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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?