Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ AZURE_AI_SPEECH_ENDPOINT="https://<speech-api-name>.cognitiveservices.azure.com/
# Chats WebSocket
# Azure Container Apps: `wss://yourcontainerapps.japaneast.azurecontainerapps.io`
CHATS_WEBSOCKET_URL="ws://localhost:8000"

# Microsoft Graph Sites
MICROSOFT_GRAPH_TENANT_ID="<YOUR_TENANT_ID>"
MICROSOFT_GRAPH_CLIENT_ID="<YOUR_CLIENT_ID>"
MICROSOFT_GRAPH_CLIENT_SECRET="<YOUR_CLIENT_SECRET>"
MICROSOFT_GRAPH_SITE_ID="<YOUR_SITE_ID>"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

site id は環境変数にせず、API の引数で指定できるようにしてください

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed MICROSOFT_GRAPH_SITE_ID from environment variables and updated all APIs to accept site_id as a parameter instead. Commit: 5fa7759

69 changes: 69 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,72 @@ az resource update \
### Azure AI Speech

- [バッチ文字起こしとは](https://learn.microsoft.com/ja-jp/azure/ai-services/speech-service/batch-transcription)

### Microsoft Graph Sites Service

Microsoft Graph Sites サービスは、SharePoint Online のファイルを操作するためのサービスです。

```shell
# Help
uv run python scripts/msgraphs_sites.py --help
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI スクリプトは scripts/msgraphs/sites.py に配置してください。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Moved CLI script to scripts/msgraphs/sites.py and updated documentation accordingly. Commit: 5fa7759


# List files in SharePoint site
uv run python scripts/msgraphs_sites.py list-files

# List files in specific folder
uv run python scripts/msgraphs_sites.py list-files --folder "Documents"

# List files in JSON format
uv run python scripts/msgraphs_sites.py list-files --format json

# Upload a single file
uv run python scripts/msgraphs_sites.py upload-file "local_file.txt"

# Upload file to specific folder
uv run python scripts/msgraphs_sites.py upload-file "local_file.txt" --folder "Documents"

# Upload file with custom name
uv run python scripts/msgraphs_sites.py upload-file "local_file.txt" --name "renamed_file.txt"

# Upload multiple files
uv run python scripts/msgraphs_sites.py upload-files "file1.txt" "file2.txt" "file3.txt"

# Upload multiple files to specific folder
uv run python scripts/msgraphs_sites.py upload-files "file1.txt" "file2.txt" --folder "Documents"

# Download a file
uv run python scripts/msgraphs_sites.py download-file "remote_file.txt"

# Download file from specific folder
uv run python scripts/msgraphs_sites.py download-file "remote_file.txt" --folder "Documents"

# Download file with custom output path
uv run python scripts/msgraphs_sites.py download-file "remote_file.txt" --output "downloaded_file.txt"

# Get file information
uv run python scripts/msgraphs_sites.py get-file-info "remote_file.txt"

# Get file information from specific folder
uv run python scripts/msgraphs_sites.py get-file-info "remote_file.txt" --folder "Documents"

# Get file information in JSON format
uv run python scripts/msgraphs_sites.py get-file-info "remote_file.txt" --format json

# Delete a file
uv run python scripts/msgraphs_sites.py delete-file "remote_file.txt"

# Delete file from specific folder
uv run python scripts/msgraphs_sites.py delete-file "remote_file.txt" --folder "Documents"

# Delete file without confirmation
uv run python scripts/msgraphs_sites.py delete-file "remote_file.txt" --force

# Delete multiple files
uv run python scripts/msgraphs_sites.py delete-files "file1.txt" "file2.txt" "file3.txt"

# Delete multiple files from specific folder
uv run python scripts/msgraphs_sites.py delete-files "file1.txt" "file2.txt" --folder "Documents"

# Delete multiple files without confirmation
uv run python scripts/msgraphs_sites.py delete-files "file1.txt" "file2.txt" --force
```
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies = [
"fastapi[standard]>=0.115.12",
"langchain-community>=0.3.27",
"langchain-openai>=0.3.27",
"msgraph-sdk>=1.9.0",
"opentelemetry-instrumentation-fastapi>=0.52b1",
"pydantic-settings>=2.10.1",
"typer>=0.16.0",
Expand Down
299 changes: 299 additions & 0 deletions scripts/msgraphs_sites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
#!/usr/bin/env python
# filepath: /home/runner/work/template-fastapi/template-fastapi/scripts/msgraphs_sites.py

import json
import os
from pathlib import Path

import typer
from rich.console import Console
from rich.table import Table

from template_fastapi.repositories.msgraphs_sites import MicrosoftGraphSitesRepository

app = typer.Typer()
console = Console()
msgraphs_sites_repo = MicrosoftGraphSitesRepository()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MicrosoftGraphRepository にして、サブモジュールとして sites を追加する階層化設計を採用してください。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Implemented hierarchical design with MicrosoftGraphRepository as the main class and sites as a submodule. Commit: 5fa7759



@app.command()
def list_files(
folder_path: str = typer.Option("", "--folder", "-f", help="フォルダパス"),
output_format: str = typer.Option("table", "--format", "-o", help="出力形式 (table/json)"),
):
"""SharePointサイトのファイル一覧を取得する"""
console.print("[bold green]SharePointサイトのファイル一覧を取得します[/bold green]")

try:
files = msgraphs_sites_repo.list_files(folder_path)

if output_format == "json":
# JSON形式で出力
files_json = [
{
"name": file.name,
"size": file.size,
"content_type": file.content_type,
"last_modified": file.last_modified.isoformat() if file.last_modified else None,
"url": file.url,
}
for file in files
]
console.print(json.dumps(files_json, indent=2, ensure_ascii=False))
else:
# テーブル形式で出力
table = Table(show_header=True, header_style="bold blue")
table.add_column("ファイル名", style="cyan")
table.add_column("サイズ", justify="right")
table.add_column("コンテンツタイプ", style="yellow")
table.add_column("最終更新日", style="green")

for file in files:
table.add_row(
file.name,
str(file.size) if file.size else "N/A",
file.content_type or "N/A",
file.last_modified.strftime("%Y-%m-%d %H:%M:%S") if file.last_modified else "N/A",
)

console.print(table)

console.print(f"✅ [bold green]{len(files)} 個のファイルを取得しました[/bold green]")

except Exception as e:
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")


@app.command()
def upload_file(
file_path: str = typer.Argument(..., help="アップロードするファイルのパス"),
folder_path: str = typer.Option("", "--folder", "-f", help="アップロード先フォルダパス"),
name: str = typer.Option(None, "--name", "-n", help="アップロード後のファイル名"),
):
"""SharePointサイトにファイルをアップロードする"""
console.print("[bold green]SharePointサイトにファイルをアップロードします[/bold green]")

try:
# ファイルの存在確認
if not os.path.exists(file_path):
console.print(f"❌ [bold red]エラー[/bold red]: ファイル '{file_path}' が見つかりません")
return

# ファイルを読み込み
with open(file_path, "rb") as f:
file_data = f.read()

# アップロード後のファイル名を決定
upload_name = name if name else Path(file_path).name

console.print(f"ファイルパス: {file_path}")
console.print(f"フォルダパス: {folder_path}")
console.print(f"アップロード名: {upload_name}")

# ファイルをアップロード
uploaded_file = msgraphs_sites_repo.upload_file(upload_name, file_data, folder_path)

console.print("✅ [bold green]ファイルが正常にアップロードされました[/bold green]")
console.print(f"ファイル名: {uploaded_file.name}")
console.print(f"サイズ: {uploaded_file.size} bytes")
console.print(f"URL: {uploaded_file.url}")

except Exception as e:
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")


@app.command()
def upload_files(
file_paths: list[str] = typer.Argument(..., help="アップロードするファイルのパス(複数指定可能)"),
folder_path: str = typer.Option("", "--folder", "-f", help="アップロード先フォルダパス"),
):
"""SharePointサイトに複数のファイルを同時にアップロードする"""
console.print("[bold green]SharePointサイトに複数のファイルをアップロードします[/bold green]")

try:
file_data_list = []

# 各ファイルを読み込み
for file_path in file_paths:
if not os.path.exists(file_path):
console.print(f"❌ [bold red]エラー[/bold red]: ファイル '{file_path}' が見つかりません")
continue

with open(file_path, "rb") as f:
file_data = f.read()

file_name = Path(file_path).name
file_data_list.append((file_name, file_data, None))

if not file_data_list:
console.print("❌ [bold red]エラー[/bold red]: アップロード可能なファイルがありません")
return

console.print(f"アップロード対象: {len(file_data_list)} 個のファイル")
console.print(f"フォルダパス: {folder_path}")

# 複数ファイルをアップロード
uploaded_files = msgraphs_sites_repo.upload_files(file_data_list, folder_path)

console.print(f"✅ [bold green]{len(uploaded_files)} 個のファイルが正常にアップロードされました[/bold green]")

# 結果を表示
table = Table(show_header=True, header_style="bold blue")
table.add_column("ファイル名", style="cyan")
table.add_column("サイズ", justify="right")
table.add_column("URL", style="yellow")

for file in uploaded_files:
table.add_row(
file.name,
str(file.size) if file.size else "N/A",
file.url or "N/A",
)

console.print(table)

except Exception as e:
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")


@app.command()
def download_file(
file_name: str = typer.Argument(..., help="ダウンロードするファイル名"),
folder_path: str = typer.Option("", "--folder", "-f", help="ファイルが保存されているフォルダパス"),
output_path: str = typer.Option("", "--output", "-o", help="保存先パス"),
):
"""SharePointサイトからファイルをダウンロードする"""
console.print("[bold green]SharePointサイトからファイルをダウンロードします[/bold green]")

try:
console.print(f"ファイル名: {file_name}")
console.print(f"フォルダパス: {folder_path}")

# ファイルをダウンロード
file_data = msgraphs_sites_repo.download_file(file_name, folder_path)

# 保存先パスを決定
save_path = output_path if output_path else file_name

# ファイルを保存
with open(save_path, "wb") as f:
f.write(file_data)

console.print("✅ [bold green]ファイルが正常にダウンロードされました[/bold green]")
console.print(f"保存先: {save_path}")
console.print(f"サイズ: {len(file_data)} bytes")

except Exception as e:
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")


@app.command()
def delete_file(
file_name: str = typer.Argument(..., help="削除するファイル名"),
folder_path: str = typer.Option("", "--folder", "-f", help="ファイルが保存されているフォルダパス"),
force: bool = typer.Option(False, "--force", help="確認なしで削除"),
):
"""SharePointサイトからファイルを削除する"""
console.print("[bold yellow]SharePointサイトからファイルを削除します[/bold yellow]")

try:
console.print(f"ファイル名: {file_name}")
console.print(f"フォルダパス: {folder_path}")

# 確認
if not force:
confirm = typer.confirm(f"ファイル '{file_name}' を削除してもよろしいですか?")
if not confirm:
console.print("削除がキャンセルされました")
return

# ファイルを削除
msgraphs_sites_repo.delete_file(file_name, folder_path)

console.print("✅ [bold green]ファイルが正常に削除されました[/bold green]")

except Exception as e:
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")


@app.command()
def delete_files(
file_names: list[str] = typer.Argument(..., help="削除するファイル名(複数指定可能)"),
folder_path: str = typer.Option("", "--folder", "-f", help="ファイルが保存されているフォルダパス"),
force: bool = typer.Option(False, "--force", help="確認なしで削除"),
):
"""SharePointサイトから複数のファイルを同時に削除する"""
console.print("[bold yellow]SharePointサイトから複数のファイルを削除します[/bold yellow]")

try:
console.print(f"削除対象: {len(file_names)} 個のファイル")
console.print(f"フォルダパス: {folder_path}")

# 確認
if not force:
confirm = typer.confirm(f"{len(file_names)} 個のファイルを削除してもよろしいですか?")
if not confirm:
console.print("削除がキャンセルされました")
return

# 複数ファイルを削除
deleted_files = msgraphs_sites_repo.delete_files(file_names, folder_path)

console.print(f"✅ [bold green]{len(deleted_files)} 個のファイルが正常に削除されました[/bold green]")

# 結果を表示
for file_name in deleted_files:
console.print(f"削除済み: {file_name}")

except Exception as e:
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")


@app.command()
def get_file_info(
file_name: str = typer.Argument(..., help="情報を取得するファイル名"),
folder_path: str = typer.Option("", "--folder", "-f", help="ファイルが保存されているフォルダパス"),
output_format: str = typer.Option("table", "--format", "-o", help="出力形式 (table/json)"),
):
"""SharePointサイトのファイル情報を取得する"""
console.print("[bold green]SharePointサイトのファイル情報を取得します[/bold green]")

try:
console.print(f"ファイル名: {file_name}")
console.print(f"フォルダパス: {folder_path}")

# ファイル情報を取得
file_info = msgraphs_sites_repo.get_file_info(file_name, folder_path)

if output_format == "json":
# JSON形式で出力
file_json = {
"name": file_info.name,
"size": file_info.size,
"content_type": file_info.content_type,
"last_modified": file_info.last_modified.isoformat() if file_info.last_modified else None,
"url": file_info.url,
}
console.print(json.dumps(file_json, indent=2, ensure_ascii=False))
else:
# テーブル形式で出力
table = Table(show_header=True, header_style="bold blue")
table.add_column("プロパティ", style="cyan")
table.add_column("値", style="yellow")

table.add_row("ファイル名", file_info.name)
table.add_row("サイズ", str(file_info.size) if file_info.size else "N/A")
table.add_row("コンテンツタイプ", file_info.content_type or "N/A")
table.add_row("最終更新日", file_info.last_modified.strftime("%Y-%m-%d %H:%M:%S") if file_info.last_modified else "N/A")
table.add_row("URL", file_info.url or "N/A")

console.print(table)

console.print("✅ [bold green]ファイル情報を取得しました[/bold green]")

except Exception as e:
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")


if __name__ == "__main__":
app()
Loading