Files
py-wikijs/wikijs/models/asset.py
Claude d2003a0005 Implement Assets API with file/asset management operations
Implementation:
- Asset data models (wikijs/models/asset.py)
  - Asset, AssetFolder models
  - AssetUpload, AssetRename, AssetMove models
  - FolderCreate model
  - File size helpers (size_mb, size_kb)
  - Field validation and normalization

- Sync AssetsEndpoint (wikijs/endpoints/assets.py)
  - list(folder_id, kind) - List assets with filtering
  - get(asset_id) - Get single asset
  - rename(asset_id, new_filename) - Rename asset
  - move(asset_id, folder_id) - Move asset between folders
  - delete(asset_id) - Delete asset
  - list_folders() - List all folders
  - create_folder(slug, name) - Create new folder
  - delete_folder(folder_id) - Delete folder
  - Note: upload/download require multipart support (future enhancement)

- Async AsyncAssetsEndpoint (wikijs/aio/endpoints/assets.py)
  - Complete async implementation
  - Identical interface to sync version
  - All asset and folder management operations

- Integration with clients
  - WikiJSClient.assets
  - AsyncWikiJSClient.assets

GraphQL operations for asset and folder management.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 20:34:50 +00:00

206 lines
5.8 KiB
Python

"""Data models for Wiki.js assets."""
from typing import Optional
from pydantic import Field, field_validator
from .base import BaseModel, TimestampedModel
class AssetFolder(BaseModel):
"""Asset folder model."""
id: int = Field(..., description="Folder ID")
slug: str = Field(..., description="Folder slug/path")
name: Optional[str] = Field(None, description="Folder name")
class Config:
"""Pydantic configuration."""
populate_by_name = True
class Asset(TimestampedModel):
"""Wiki.js asset model.
Represents a file asset (image, document, etc.) in Wiki.js.
Attributes:
id: Asset ID
filename: Original filename
ext: File extension
kind: Asset kind (image, binary, etc.)
mime: MIME type
file_size: File size in bytes
folder_id: Parent folder ID
folder: Parent folder information
author_id: ID of user who uploaded
author_name: Name of user who uploaded
created_at: Upload timestamp
updated_at: Last update timestamp
"""
id: int = Field(..., description="Asset ID")
filename: str = Field(..., min_length=1, description="Original filename")
ext: str = Field(..., description="File extension")
kind: str = Field(..., description="Asset kind (image, binary, etc.)")
mime: str = Field(..., description="MIME type")
file_size: int = Field(
..., alias="fileSize", ge=0, description="File size in bytes"
)
folder_id: Optional[int] = Field(
None, alias="folderId", description="Parent folder ID"
)
folder: Optional[AssetFolder] = Field(None, description="Parent folder")
author_id: Optional[int] = Field(None, alias="authorId", description="Author ID")
author_name: Optional[str] = Field(
None, alias="authorName", description="Author name"
)
@field_validator("filename")
@classmethod
def validate_filename(cls, v: str) -> str:
"""Validate filename."""
if not v or not v.strip():
raise ValueError("Filename cannot be empty")
return v.strip()
@property
def size_mb(self) -> float:
"""Get file size in megabytes."""
return self.file_size / (1024 * 1024)
@property
def size_kb(self) -> float:
"""Get file size in kilobytes."""
return self.file_size / 1024
class Config:
"""Pydantic configuration."""
populate_by_name = True
class AssetUpload(BaseModel):
"""Model for uploading a new asset.
Attributes:
file_path: Local path to file to upload
folder_id: Target folder ID (default: 0 for root)
filename: Optional custom filename (uses file_path name if not provided)
"""
file_path: str = Field(..., alias="filePath", description="Local file path")
folder_id: int = Field(default=0, alias="folderId", description="Target folder ID")
filename: Optional[str] = Field(None, description="Custom filename")
@field_validator("file_path")
@classmethod
def validate_file_path(cls, v: str) -> str:
"""Validate file path."""
if not v or not v.strip():
raise ValueError("File path cannot be empty")
return v.strip()
class Config:
"""Pydantic configuration."""
populate_by_name = True
class AssetRename(BaseModel):
"""Model for renaming an asset.
Attributes:
asset_id: Asset ID to rename
new_filename: New filename
"""
asset_id: int = Field(..., alias="assetId", description="Asset ID")
new_filename: str = Field(
..., alias="newFilename", min_length=1, description="New filename"
)
@field_validator("asset_id")
@classmethod
def validate_asset_id(cls, v: int) -> int:
"""Validate asset ID."""
if v <= 0:
raise ValueError("Asset ID must be positive")
return v
@field_validator("new_filename")
@classmethod
def validate_filename(cls, v: str) -> str:
"""Validate filename."""
if not v or not v.strip():
raise ValueError("Filename cannot be empty")
return v.strip()
class Config:
"""Pydantic configuration."""
populate_by_name = True
class AssetMove(BaseModel):
"""Model for moving an asset to a different folder.
Attributes:
asset_id: Asset ID to move
folder_id: Target folder ID
"""
asset_id: int = Field(..., alias="assetId", description="Asset ID")
folder_id: int = Field(..., alias="folderId", description="Target folder ID")
@field_validator("asset_id")
@classmethod
def validate_asset_id(cls, v: int) -> int:
"""Validate asset ID."""
if v <= 0:
raise ValueError("Asset ID must be positive")
return v
@field_validator("folder_id")
@classmethod
def validate_folder_id(cls, v: int) -> int:
"""Validate folder ID."""
if v < 0:
raise ValueError("Folder ID must be non-negative")
return v
class Config:
"""Pydantic configuration."""
populate_by_name = True
class FolderCreate(BaseModel):
"""Model for creating a new folder.
Attributes:
slug: Folder slug/path
name: Optional folder name
"""
slug: str = Field(..., min_length=1, description="Folder slug/path")
name: Optional[str] = Field(None, description="Folder name")
@field_validator("slug")
@classmethod
def validate_slug(cls, v: str) -> str:
"""Validate slug."""
if not v or not v.strip():
raise ValueError("Slug cannot be empty")
# Remove leading/trailing slashes
v = v.strip().strip("/")
if not v:
raise ValueError("Slug cannot be just slashes")
return v
class Config:
"""Pydantic configuration."""
populate_by_name = True