PythonでBOXアップロード/削除してみた

目次

目的

業務上Linux系のサーバからBOXにアップロードするような要件が発生したため、Pythonで実装しました。

仕様

  • python3 系で実装
  • BOX SDKを使用して実装
  • プロキシを使用する環境に対応
  • BOXへのファイル/フォルダ アップロード/削除に対応
  • フォルダを指定した場合、再帰的にフォルダを作成する(階層を維持する)
  • BOXへのファイルアップロードが失敗した場合は3回リトライ

モジュールインストール

#モジュールインストール
python3 -m pip install boxsdk

コード

  • 認証用のconfig.jsonはBOXは、「Enterprise Setting」の「Apps」より事前にダウンロードする。
  • configは任意の場所に保存し、/path/config.jsonを書き換える。configは読み取り権限に設定する。
  • Proxy.URL は環境にあわせて設定が必要。不要な場合はコメントアウトする。
import os
import time
import logging
import math
from boxsdk import BoxAPIException, JWTAuth, Client
from boxsdk.object.folder import Folder
from boxsdk.object.file import File
from boxsdk.config import Proxy

# プロキシ設定
Proxy.URL = 'http://192.168.1.100:8080'

# デフォルトの設定ファイルパス
DEFAULT_JSON_FILE_PATH = '/path/config.json'

# ログの設定
LOG_FILE_PATH = '/var/log/box.log'
logging.basicConfig(filename=LOG_FILE_PATH, level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')

# Box クライアントの初期化
def init_box_client(json_file_path: str = DEFAULT_JSON_FILE_PATH):
    """
    Box クライアントを初期化します。

    Args:
        json_file_path (str, optional): Box 認証用の JSON ファイルのパス。デフォルトはデフォルトのパスです。
    """
    auth = JWTAuth.from_settings_file(json_file_path)
    return Client(auth)

# ファイルのアップロード
def upload_file(file_path: str, folder_id: str, retry_count: int = 3):
    """
    ファイルを Box にアップロードします。

    Args:
        file_path (str): アップロードするローカルファイルのパス。
        folder_id (str): ファイルをアップロードする Box フォルダの ID。
        client (Client): Box クライアントインスタンス。
        retry_count (int, optional): 失敗した場合のリトライ回数。デフォルトは 3 回。
    """
    client = init_box_client() # BOXクライアント初期化
    # リトライ回数分繰り返す
    for _ in range(retry_count):
        try:
            # ファイルのサイズと名前を取得
            file_size = os.path.getsize(file_path)
            file_name = os.path.basename(file_path)
            # Box フォルダを取得
            folder = client.folder(folder_id).get()
            # プレフライトチェックを実行
            folder.preflight_check(file_size, file_name)
            # ファイルをアップロード
            client.folder(folder_id).upload(file_path, file_name, preflight_check=False)
            return  # アップロード成功のため、関数を終了
        except BoxAPIException as err:
            logging.error(f"ファイルのアップロードに失敗しました: {err}")
            time.sleep(1)  # リトライ前に 1 秒待機
    raise RuntimeError("複数回のリトライ後にファイルをアップロードできませんでした")

# フォルダのアップロード
def upload_folder(local_folder_path: str, box_parent_folder_id: str, min_file_size: int = 1024 * 1024 * 20, retry_count: int = 3):
    """
    フォルダとその内容を Box にアップロードします。

    Args:
        local_folder_path (str): アップロードするローカルフォルダのパス。
        box_parent_folder_id (str): フォルダをアップロードする Box 親フォルダの ID。
        min_file_size (int, optional): チャンクアップロードのための最小ファイルサイズ。デフォルトは 20MB です。
        retry_count (int, optional): 失敗した場合のリトライ回数。デフォルトは 3 回 です。
    """
    client = init_box_client()  # BOXクライアント初期化
    # ローカルフォルダのパスを絶対パスに変換
    local_folder = os.path.abspath(local_folder_path)
    # フォルダ名を取得
    folder_name = os.path.basename(local_folder_path)
    try:
        # Box に新しいフォルダを作成
        new_box_folder = create_box_folder(folder_name, box_parent_folder_id, client)
        # フォルダ内のアイテムを反復処理
        for item in os.listdir(local_folder):
            item_path = os.path.join(local_folder, item)
            # サブフォルダを再帰的にアップロード
            if os.path.isdir(item_path):
                # サブフォルダ内のファイルとサブフォルダをアップロード
                upload_folder(item_path, new_box_folder.id, min_file_size=min_file_size, retry_count=retry_count)
            else:
                try:
                    # ファイルサイズに基づいてファイルまたはチャンクアップロードを実行
                    if os.path.getsize(item_path) < min_file_size:
                        upload_file(item_path, new_box_folder.id, retry_count=retry_count)
                    else:
                        upload_file_chunked(item_path, new_box_folder.id, client, retry_count=retry_count)
                except Exception as e:
                    logging.error(f"{item_path} のアップロードに失敗しました: {e}")
    except Exception as e:
        logging.error(f"フォルダの作成に失敗しました: {e}")

# Box にフォルダを作成
def create_box_folder(folder_name: str, parent_folder_id: str, client: Client) -> Folder:
    """
    Box にフォルダを作成します。

    Args:
        folder_name (str): 作成するフォルダの名前。
        parent_folder_id (str): フォルダが作成される親フォルダの ID。
        client (Client): Box クライアントインスタンス。

    Returns:
        Folder: 作成された Box フォルダオブジェクト。
    """
    try:
        folder = client.folder(parent_folder_id).create_subfolder(folder_name)
    except BoxAPIException as err:
        if err.code == "item_name_in_use":
            folder_id = err.context_info["conflicts"][0]["id"]
            folder = client.folder(folder_id).get()
        else:
            raise err
    return folder

# Box 内のアイテムを削除
def delete_item(item_id: str, item_type: str):
    """
    Box 内のアイテムを削除します。

    Args:
        item_id (str): アイテムの ID。
        item_type (str): アイテムの種類('file' または 'folder')。
        client (Client): Box クライアントインスタンス。
    """
    client = init_box_client() # BOXクライアント初期化
    try:
        if item_type == 'file':
            client.file(item_id).delete()
        elif item_type == 'folder':
            client.folder(item_id).delete()
        else:
            raise ValueError("無効なアイテムタイプです。'file' または 'folder' を指定してください。")
    except BoxAPIException as err:
        logging.error(f"{item_type} の削除に失敗しました: {err}")

def upload_file_chunked(file_path: str, folder_id: str, client: Client, chunk_size: int = 1024 * 1024 * 100, retry_count: int = 3):
    """
    ファイルをチャンク単位で分割して Box にアップロードします。

    Args:
        file_path (str): アップロードするローカルファイルのパス。
        folder_id (str): ファイルをアップロードする Box フォルダの ID。
        client (Client): Box クライアントインスタンス。
        chunk_size (int, optional): チャンクのサイズ。デフォルトは 100MB です。
        retry_count (int, optional): 失敗した場合のリトライ回数。デフォルトは 3 回です。
    """
    # リトライ回数分繰り返す
    for _ in range(retry_count):
        try:
            # ファイルのサイズと名前を取得
            file_size = os.path.getsize(file_path)
            file_name = os.path.basename(file_path)
            # Box フォルダを取得
            folder = client.folder(folder_id).get()
            # プレフライトチェックを実行
            folder.preflight_check(file_size, file_name)
            # ファイルをチャンク単位で分割してアップロード
            with open(file_path, 'rb') as file_stream:
                upload_session = folder.create_upload_session(file_name, file_size)
                total_parts = math.ceil(file_size / chunk_size)
                for part_index in range(total_parts):
                    offset = part_index * chunk_size
                    chunk = file_stream.read(chunk_size)
                    upload_session.upload_part_bytes(chunk, offset, part_index + 1, total_parts)
                upload_session.commit()
            return  # アップロード成功のため、関数を終了
        except BoxAPIException as err:
            logging.error(f"アップロードに失敗しました: {err}")
            time.sleep(1)  # リトライ前に 1 秒待機
    raise RuntimeError("複数回のリトライ後にファイルをアップロードできませんでした")

if __name__ == "__main__":
    # アップロードするローカルフォルダのパス
    local_folder_path = "/path/to/local/folder"

    # アップロード先の Box フォルダの ID
    box_folder_id = "0"  # フォルダの ID を指定

    # Box クライアントを初期化
    client = init_box_client()

    # フォルダのアップロードを実行
    upload_folder(local_folder_path, box_folder_id)

実行

  • スクリプト名は「box_exec.py」とする

ファイルアップロード

python3 -c "import box_exec; box_exec.upload_folder("/tmp/hogehoge", "123456789012")"
  • 第一引数(/tmp/hogehoge)はアップロード対象
  • 第二引数(123456789012)はアップロード先オブジェクトID

フォルダアップロード

python3 -c "import box_exec; box_exec.upload_folder("/tmp/hogehoge", "123456789012")"
  • 第一引数(/tmp/hogehoge)はアップロード対象
  • 第二引数(123456789012)はアップロード先オブジェクトID

ファイル/フォルダ削除

python3 -c "import box_exec; box_exec.delete_item("123456789012", "file|folder")"
  • 第一引数(123456789012)は削除対象のフォルダ・ファイル識別ID
  • 第2引数は削除対象オブジェクトの種別を指定
    • file= ファイル
    • folder = フォルダ

参考

あわせて読みたい
SDKを使用したJWT - Box Developerドキュメント ここではアプリの開発に使用できるBoxのAPIやSDK、 APIドキュメント、開発者向けサポートリソースを探したり、 Box開発者コンソールにアクセスしたりできます
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次