kumiho
Kumiho Python Client Library.
Kumiho is a graph-native creative and AI asset management system that tracks revisions, relationships, and lineage without uploading original files to the cloud. This SDK provides a Pythonic interface to the Kumiho gRPC backend.
- Getting Started:
The simplest way to use Kumiho is with the top-level functions that use a default client configured from your environment:
import kumiho # Authenticate and configure (run once per session) kumiho.auto_configure_from_discovery() # This can be avoided if env var KUMIHO_AUTO_CONFIGURE=1 is set # Create a project project = kumiho.create_project("my-vfx-project", "VFX assets for commercial") # Create spaces and items space = project.create_space("characters") item = space.create_item("hero", "model") # Create revisions and artifacts revision = item.create_revision() artifact = revision.create_artifact("main", "/path/to/hero.fbx")
For more control, you can create a client manually:
import kumiho client = kumiho.connect( endpoint="localhost:50051", token="your-auth-token" ) with kumiho.use_client(client): projects = kumiho.get_projects()
- Key Concepts:
Project: Top-level container for all assets and spaces.
Space: Hierarchical folder structure within a project.
Item: A versioned asset (model, texture, workflow, etc.).
Revision: A specific iteration of an item with artifacts.
Artifact: A file reference (path/URI) within a revision.
Edge: A relationship between revisions (dependencies, references).
Kref: A URI-based unique identifier for any Kumiho object.
- Authentication:
Kumiho uses Firebase authentication. Run the CLI to log in:
kumiho-auth login
This caches credentials in
~/.kumiho/. Then useauto_configure_from_discovery()to bootstrap the client.- Environment Variables:
KUMIHO_AUTO_CONFIGURE: Set to “1” to auto-configure on import.KUMIHO_AUTH_TOKEN: Override the authentication token.KUMIHO_CONTROL_PLANE_URL: Override the control plane URL.KUMIHO_ENDPOINT: Override the gRPC endpoint.
Example
Complete workflow example:
import kumiho
# Configure client from cached credentials
kumiho.auto_configure_from_discovery()
# Get or create project
project = kumiho.get_project("my-project")
if not project:
project = kumiho.create_project("my-project", "My VFX project")
# Navigate to asset space
assets = project.get_space("assets") or project.create_space("assets")
# Create a new model item
model = assets.create_item("character", "model")
# Create first revision
v1 = model.create_revision(metadata={"author": "artist1"})
v1.create_artifact("mesh", "/projects/char/v1/mesh.fbx")
v1.create_artifact("textures", "/projects/char/v1/textures.zip")
v1.tag("approved")
v1.tag("published") # published tag is reserved tag within Kumiho as revision with immutable semantics
# Query by kref
item = kumiho.get_item("kref://my-project/assets/character.model")
revision = kumiho.get_revision("kref://my-project/assets/character.model?r=1")
# Search across project
models = kumiho.item_search(
context_filter="my-project",
kind_filter="model"
)
Note
Kumiho follows a “BYO Storage” philosophy—files remain on your local or network storage. Kumiho only tracks paths, metadata, and relationships in its graph database.
See also
Kumiho documentation: https://docs.kumiho.cloud
- class kumiho.KumihoObject[source]
Bases:
objectBase class for all high-level Kumiho domain objects.
This abstract base class provides common functionality shared by all Kumiho objects, including access to the client for making API calls.
All domain objects (
Project,Space,Item,Revision,Artifact,Edge) inherit from this class.- _client
The client instance for making API calls (internal).
Note
This is an internal base class. Users typically interact with concrete subclasses like
ProjectorVersion.
- exception kumiho.KumihoError[source]
Bases:
ExceptionBase exception class for all Kumiho errors.
All custom exceptions raised by the Kumiho SDK inherit from this class, making it easy to catch all Kumiho-related errors.
Example:
import kumiho try: project = kumiho.get_project("nonexistent") except kumiho.KumihoError as e: print(f"Kumiho error: {e}")
- class kumiho.Project[source]
Bases:
KumihoObjectA Kumiho project—the top-level container for assets.
Projects are the root of the Kumiho hierarchy. Each project has its own namespace for spaces and items, and manages access control and settings independently.
Projects support both public and private access modes, allowing you to share assets publicly or restrict them to authenticated users.
Example
Basic project operations:
import kumiho # Get existing project project = kumiho.get_project("my-project") # Create spaces assets = project.create_space("assets") shots = project.create_space("shots") # Navigate to nested spaces char_space = project.get_space("assets/characters") # List all spaces recursively for space in project.get_spaces(recursive=True): print(f" {space.path}") # Update project settings project.set_public(True) # Enable public access project.update(description="Updated description") # Soft delete (deprecate) project.delete() # Hard delete (permanent) project.delete(force=True)
- __init__(pb, client)[source]
Initialize a Project from a protobuf response.
- Parameters:
pb (
ProjectResponse) – The protobuf ProjectResponse message.client (
_Client) – The client instance for making API calls.
- Return type:
None
- create_bundle(bundle_name, parent_path=None, metadata=None)[source]
Create a new bundle within this project.
Bundles are special items that aggregate other items. They provide a way to group related items together and maintain an audit trail of membership changes through revision history.
- Parameters:
bundle_name (
str) – The name of the bundle. Must be unique within the parent space.parent_path (
Optional[str]) – Optional parent path for the bundle. If not provided, creates the bundle at the project root (/{project_name}).metadata (
Optional[Dict[str,str]]) – Optional key-value metadata for the bundle.
- Returns:
The newly created Bundle object with kind “bundle”.
- Return type:
- Raises:
grpc.RpcError – If the bundle name is already taken or if there is a connection error.
See also
Bundle: The Bundle class.create_bundle(): Create bundle in a space.Example:
>>> project = kumiho.get_project("film-2024") >>> # Create at project root >>> bundle = project.create_bundle("release-bundle") >>> >>> # Create in specific space >>> bundle = project.create_bundle( ... "character-bundle", ... parent_path="/film-2024/assets" ... ) >>> >>> # Add items to the bundle >>> hero = project.get_space("models").get_item("hero", "model") >>> bundle.add_member(hero)
- create_item(item_name, kind, parent_path=None, metadata=None)[source]
Create a new item within this project.
- Parameters:
item_name (
str) – The name of the item. Must be unique within the parent space combined with the kind.kind (
str) – The type/kind of the item (e.g., “model”, “texture”).parent_path (
Optional[str]) – Optional parent path for the item. If not provided, creates the item at the project root (/{project_name}).metadata (
Optional[Dict[str,str]]) – Optional key-value metadata for the item.
- Returns:
The newly created Item object.
- Return type:
- Raises:
grpc.RpcError – If the item name/kind combination is already taken or if there is a connection error.
Example:
>>> project = kumiho.get_project("film-2024") >>> # Create at project root >>> item = project.create_item("hero", "model") >>> >>> # Create in specific space >>> item = project.create_item( ... "hero", ... "texture", ... parent_path="/film-2024/assets" ... )
- create_space(name, parent_path=None)[source]
Create a space within this project.
- Parameters:
- Returns:
The newly created Space object.
- Return type:
Example
>>> project = kumiho.get_project("film-2024") >>> # Create at root >>> chars = project.create_space("characters") >>> # Create nested space >>> heroes = project.create_space("heroes", parent_path="/film-2024/characters")
- delete(force=False)[source]
Delete or deprecate this project.
- Parameters:
force (
bool) – If True, permanently delete the project and all its contents. If False (default), mark as deprecated.- Returns:
Response indicating success or failure.
- Return type:
StatusResponse
Warning
Force deletion is irreversible and removes all spaces, items, revisions, artifacts, and edges within the project.
Example
>>> project = kumiho.get_project("old-project") >>> # Soft delete (can be recovered) >>> project.delete() >>> # Hard delete (permanent) >>> project.delete(force=True)
- get_bundle(bundle_name, parent_path=None)[source]
Get an existing bundle within this project.
- Parameters:
- Returns:
The Bundle object.
- Return type:
- Raises:
ValueError – If the item exists but is not a bundle.
grpc.RpcError – If the bundle is not found.
Example:
>>> project = kumiho.get_project("film-2024") >>> # Get from project root >>> bundle = project.get_bundle("release-bundle") >>> >>> # Get from specific space >>> bundle = project.get_bundle( ... "character-bundle", ... parent_path="/film-2024/assets" ... ) >>> members = bundle.get_members()
- get_item(item_name, kind, parent_path=None)[source]
Get an existing item within this project.
- Parameters:
- Returns:
The Item object.
- Return type:
- Raises:
grpc.RpcError – If the item is not found.
Example:
>>> project = kumiho.get_project("film-2024") >>> # Get from project root >>> item = project.get_item("hero", "model") >>> >>> # Get from specific space >>> item = project.get_item( ... "hero", ... "texture", ... parent_path="/film-2024/assets" ... )
- get_space(name, parent_path=None)[source]
Get an existing space within this project.
- Parameters:
- Returns:
The Space object.
- Return type:
- Raises:
grpc.RpcError – If the space is not found.
Example
>>> # Get by absolute path >>> space = project.get_space("/film-2024/characters")
>>> # Get by relative name (from project root) >>> space = project.get_space("characters")
>>> # Get nested space with parent path >>> heroes = project.get_space("heroes", parent_path="/film-2024/characters")
- get_spaces(parent_path=None, recursive=False)[source]
List spaces within this project.
- Parameters:
- Returns:
A list of Space objects.
- Return type:
List[Space]
Example
>>> # List direct children only >>> spaces = project.get_spaces() >>> for s in spaces: ... print(s.name)
>>> # List all spaces recursively >>> all_spaces = project.get_spaces(recursive=True) >>> for s in all_spaces: ... print(s.path)
- set_public(public)[source]
Set whether this project allows anonymous read access.
- Parameters:
public (
bool) – True to enable public access, False to require authentication for all access.- Returns:
The updated Project object.
- Return type:
Example
>>> project.set_public(True) # Enable public access >>> project.set_public(False) # Require authentication
- class kumiho.Space[source]
Bases:
KumihoObjectA hierarchical container for organizing items in Kumiho.
Spaces form the folder structure within a project. They can contain other spaces (subspaces) and items, allowing you to organize assets in a meaningful hierarchy.
Spaces are identified by their full path (e.g., “/project/characters/heroes”) and can store custom metadata.
Example
Creating and navigating spaces:
import kumiho project = kumiho.get_project("my-project") # Create a space assets = project.create_space("assets") # Create subspaces models = assets.create_space("models") textures = assets.create_space("textures") # Create items chair = models.create_item("chair", "model") # List items with filters all_models = models.get_items() wood_textures = textures.get_items(name_filter="wood*") # Navigate hierarchy parent = models.get_parent_space() # Returns assets project = models.get_project() # Returns my-project
- __init__(pb_space, client)[source]
Initialize a Space from a protobuf response.
- Parameters:
pb_space (
SpaceResponse) – The protobuf SpaceResponse message.client (
_Client) – The client instance for making API calls.
- Return type:
None
- create_bundle(bundle_name, metadata=None)[source]
Create a new bundle within this space.
Bundles are special items that aggregate other items. They provide a way to group related items together and maintain an audit trail of membership changes through revision history.
- Parameters:
- Returns:
The newly created Bundle object with kind “bundle”.
- Return type:
- Raises:
grpc.RpcError – If the bundle name is already taken or if there is a connection error.
See also
Bundle: The Bundle class.create_bundle(): Create bundle in a project.Example:
>>> # Create a bundle for a character bundle >>> assets = project.get_space("assets") >>> bundle = assets.create_bundle("character-bundle") >>> >>> # Add items to the bundle >>> hero = assets.get_item("hero", "model") >>> bundle.add_member(hero)
- create_item(item_name, kind)[source]
Create a new item within this space.
Items are versioned assets that can contain multiple artifacts. The combination of name and kind must be unique within the space.
- Parameters:
- Returns:
The newly created Item object.
- Return type:
Example
>>> models = project.get_space("models") >>> chair = models.create_item("office-chair", "model") >>> wood = textures.create_item("oak-wood", "texture")
- create_space(name)[source]
Create a new subspace within this space.
- Parameters:
name (
str) – The name of the new subspace.- Returns:
The newly created Space object.
- Return type:
Example
>>> assets = project.create_space("assets") >>> models = assets.create_space("models") >>> textures = assets.create_space("textures")
- delete(force=False)[source]
Delete this space.
- Parameters:
force (
bool) – If True, force deletion even if the space contains items. If False (default), deletion fails if space is not empty.- Raises:
grpc.RpcError – If deletion fails (e.g., space not empty and force=False).
- Return type:
Example
>>> # Delete empty space >>> empty_space.delete()
>>> # Force delete space with contents >>> old_space.delete(force=True)
- delete_attribute(key)[source]
Delete a single metadata attribute.
- Parameters:
key (
str) – The attribute key to delete.- Returns:
True if the attribute was deleted successfully.
- Return type:
Example
>>> space.delete_attribute("old_field") True
- get_attribute(key)[source]
Get a single metadata attribute.
- Parameters:
key (
str) – The attribute key to retrieve.- Return type:
- Returns:
The attribute value if it exists, None otherwise.
Example
>>> space.get_attribute("department") "modeling"
- get_bundle(bundle_name)[source]
Get a bundle by name from this space.
This is a convenience method that fetches a bundle item and returns it as a Bundle object with bundle-specific methods like add_member(), get_members(), etc.
- Parameters:
bundle_name (
str) – The name of the bundle.- Returns:
The Bundle object.
- Return type:
- Raises:
grpc.RpcError – If the bundle is not found.
Example
>>> bundle = space.get_bundle("character-bundle") >>> members = bundle.get_members() >>> for member in members: ... print(member.item_kref)
- get_child_spaces()[source]
Get immediate child spaces of this space.
This is a convenience method equivalent to
get_spaces(recursive=False).- Returns:
A list of direct child Space objects.
- Return type:
List[Space]
Example
>>> assets = project.get_space("assets") >>> children = assets.get_child_spaces() >>> for child in children: ... print(child.name)
- get_item(item_name, kind)[source]
Get a specific item by name and kind.
- Parameters:
- Returns:
The Item object.
- Return type:
- Raises:
grpc.RpcError – If the item is not found.
Example
>>> chair = models.get_item("office-chair", "model") >>> revisions = chair.get_revisions()
- get_items(item_name_filter='', kind_filter='')[source]
List items within this space with optional filtering.
- Parameters:
- Returns:
A list of Item objects matching the filters.
- Return type:
List[Item]
Example
>>> # All items in space >>> items = space.get_items()
>>> # Only models >>> models = space.get_items(kind_filter="model")
>>> # Items starting with "hero" >>> heroes = space.get_items(item_name_filter="hero*")
- get_parent_space()[source]
Get the parent space of this space.
- Returns:
- The parent Space object, or None if this is
a project-level root space.
- Return type:
Optional[Space]
Example
>>> heroes = project.get_space("characters/heroes") >>> chars = heroes.get_parent_space() # Returns "characters" space >>> root = chars.get_parent_space() # Returns None (project root)
- get_project()[source]
Get the project that contains this space.
- Returns:
The parent Project object.
- Return type:
Example
>>> space = kumiho.get_item("kref://my-project/assets/hero.model").get_space() >>> project = space.get_project() >>> print(project.name) my-project
- get_space(name)[source]
Get an existing subspace by name.
- Parameters:
name (
str) – The name of the subspace (not full path).- Returns:
The Space object.
- Return type:
- Raises:
grpc.RpcError – If the space is not found.
Example
>>> assets = project.get_space("assets") >>> models = assets.get_space("models")
- get_spaces(recursive=False)[source]
List child spaces under this space.
- Parameters:
recursive (
bool) – If True, include all nested spaces. If False (default), only direct children.- Returns:
A list of Space objects.
- Return type:
List[Space]
Example
>>> # Direct children only >>> children = space.get_spaces()
>>> # All nested spaces >>> all_spaces = space.get_spaces(recursive=True)
- set_attribute(key, value)[source]
Set a single metadata attribute.
This allows granular updates to metadata without replacing the entire metadata map.
- Parameters:
- Returns:
True if the attribute was set successfully.
- Return type:
Example
>>> space.set_attribute("department", "modeling") True
- set_metadata(metadata)[source]
Set or update metadata for this space.
Metadata is a dictionary of string key-value pairs that can store any custom information about the space.
- Parameters:
metadata (
Dict[str,str]) – Dictionary of metadata to set. Existing keys are overwritten, new keys are added.- Returns:
The updated Space object.
- Return type:
Example
>>> space.set_metadata({ ... "department": "modeling", ... "supervisor": "jane.doe", ... "status": "active" ... })
- class kumiho.Item[source]
Bases:
KumihoObjectA versioned asset in the Kumiho system.
Items represent assets that can have multiple revisions, such as 3D models, textures, workflows, or any other type of creative content. Each item belongs to a space and is identified by a combination of name and kind.
The item’s kref (Kumiho Reference) is a URI that uniquely identifies it:
kref://project/space/item.kindExample
Basic item operations:
import kumiho # Get item by kref item = kumiho.get_item("kref://film/chars/hero.model") # Create revisions v1 = item.create_revision() v2 = item.create_revision(metadata={"notes": "Updated mesh"}) # Get specific revision v1 = item.get_revision(1) latest = item.get_latest_revision() # Get revision by tag approved = item.get_revision_by_tag("approved") # Get revision at a specific time historical = item.get_revision_by_time("202312011200") # Set metadata item.set_metadata({"status": "final", "priority": "high"}) # Deprecate the item item.set_deprecated(True)
- __init__(pb_item, client)[source]
Initialize an Item from a protobuf response.
- Parameters:
pb_item (
ItemResponse) – The protobuf ItemResponse message.client (
_Client) – The client instance for making API calls.
- Return type:
None
- create_revision(metadata=None, number=0)[source]
Create a new revision of this item.
Revisions are automatically numbered sequentially. Each revision starts with the “latest” tag, which moves to the newest revision.
- Parameters:
- Returns:
The newly created Revision object.
- Return type:
Example
>>> # Auto-numbered revision >>> v1 = item.create_revision() >>> v2 = item.create_revision(metadata={"artist": "jane"})
>>> # Specific revision number (use with caution) >>> v5 = item.create_revision(number=5)
- delete(force=False)[source]
Delete this item.
- Parameters:
force (
bool) – If True, permanently delete the item and all its revisions. If False (default), deletion may fail if the item has revisions.- Raises:
grpc.RpcError – If deletion fails.
- Return type:
Example
>>> # Delete item (fails if has revisions) >>> item.delete()
>>> # Force delete with all revisions >>> item.delete(force=True)
- delete_attribute(key)[source]
Delete a single metadata attribute.
- Parameters:
key (
str) – The attribute key to delete.- Returns:
True if the attribute was deleted successfully.
- Return type:
Example
>>> item.delete_attribute("old_field") True
- get_attribute(key)[source]
Get a single metadata attribute.
- Parameters:
key (
str) – The attribute key to retrieve.- Return type:
- Returns:
The attribute value if it exists, None otherwise.
Example
>>> item.get_attribute("status") "final"
- get_latest_revision()[source]
Get the latest revision of this item.
The latest revision is the one with the “latest” tag, or if none exists, the revision with the highest number.
- Returns:
- The latest Revision object, or None if no
revisions exist.
- Return type:
Optional[Revision]
Example
>>> latest = item.get_latest_revision() >>> if latest: ... print(f"Latest: v{latest.number}")
- get_project()[source]
Get the project that contains this item.
- Returns:
The parent Project object.
- Return type:
Example
>>> project = item.get_project() >>> print(project.name)
- get_revision(revision_number)[source]
Get a specific revision by its number.
- Parameters:
revision_number (
int) – The revision number to retrieve (1-based).- Returns:
The Revision object if found, None otherwise.
- Return type:
Optional[Revision]
Example
>>> v3 = item.get_revision(3) >>> if v3: ... artifacts = v3.get_artifacts()
- get_revision_by_tag(tag)[source]
Get a revision by its tag.
Common tags include “latest”, “published”, “approved”, etc. Custom tags can be applied to revisions using
Revision.tag().- Parameters:
tag (
str) – The tag to search for.- Returns:
The Revision object if found, None otherwise.
- Return type:
Optional[Revision]
Example
>>> approved = item.get_revision_by_tag("approved") >>> published = item.get_revision_by_tag("published")
- get_revision_by_time(time, tag=None)[source]
Get the revision that had a specific tag at a given time.
This is essential for reproducible builds and historical queries. By combining a tag (like “published”) with a timestamp, you can answer questions like “What was the published version on June 1st?”
- Parameters:
time (
Union[str,datetime]) – The time as a datetime object, or a string in either YYYYMMDDHHMM format (e.g., “202312251430”) or RFC3339 format (e.g., “2023-12-25T14:30:00Z”).tag (
Optional[str]) – Optional tag to filter by. Common values: - “published”: Find the published revision at that time - “approved”: Find the approved revision at that time - None (default): Find the latest revision at that time
- Returns:
- The Revision that had the specified tag
at that time, or None if not found.
- Return type:
Optional[Revision]
Example
>>> from datetime import datetime, timezone
>>> # Get the published revision as of June 1st, 2024 >>> june_1 = datetime(2024, 6, 1, tzinfo=timezone.utc) >>> rev = item.get_revision_by_time(june_1, tag="published")
>>> # Get whatever was latest at a specific time >>> rev = item.get_revision_by_time("202312251430")
>>> # Using RFC3339 format with published tag >>> rev = item.get_revision_by_time( ... "2024-06-01T00:00:00Z", ... tag="published" ... )
Note
This is particularly useful with the “published” tag since published revisions are immutable and represent stable, approved versions suitable for downstream consumption.
- get_revisions()[source]
Get all revisions of this item.
- Returns:
A list of Revision objects, ordered by revision number.
- Return type:
List[Revision]
Example
>>> revisions = item.get_revisions() >>> for v in revisions: ... print(f"v{v.number}: created {v.created_at}")
- get_space()[source]
Get the space that contains this item.
- Returns:
The parent Space object.
- Return type:
Example
>>> item = kumiho.get_item("kref://project/chars/hero.model") >>> space = item.get_space() >>> print(space.path) # "/project/chars"
- peek_next_revision()[source]
Get the next revision number that would be assigned.
This is useful for previewing revision numbers before creating revisions, such as for naming files or planning workflows.
- Returns:
The next revision number.
- Return type:
Example
>>> next_num = item.peek_next_revision() >>> print(f"Next revision will be v{next_num}")
- set_attribute(key, value)[source]
Set a single metadata attribute.
This allows granular updates to metadata without replacing the entire metadata map.
- Parameters:
- Returns:
True if the attribute was set successfully.
- Return type:
Example
>>> item.set_attribute("status", "final") True
- set_deprecated(status)[source]
Set the deprecated status of this item.
Deprecated items are hidden from default searches but remain accessible for historical reference.
Example
>>> item.set_deprecated(True) # Hide from searches >>> item.set_deprecated(False) # Restore visibility
- set_metadata(metadata)[source]
Set or update metadata for this item.
Metadata is merged with existing metadata—existing keys are overwritten and new keys are added.
- Parameters:
metadata (
Dict[str,str]) – Dictionary of metadata key-value pairs.- Returns:
The updated Item object.
- Return type:
Example
>>> item.set_metadata({ ... "status": "final", ... "department": "modeling", ... "complexity": "high" ... })
- class kumiho.Revision[source]
Bases:
KumihoObjectA specific iteration of an item in the Kumiho system.
Revisions are immutable snapshots of an item at a point in time. Each revision can have multiple artifacts (file references), tags for categorization, and edges to other revisions for dependency tracking.
The revision’s kref includes the revision number:
kref://project/space/item.kind?r=1Revisions support dynamic tag checking—the
tagsproperty automatically refreshes from the server if the local data might be stale (older than 5 seconds). This ensures tags like “latest” are always current.Example
Creating and managing revisions:
import kumiho item = kumiho.get_item("kref://project/models/hero.model") # Create a revision with metadata v1 = item.create_revision(metadata={ "artist": "jane.doe", "software": "maya-2024", "notes": "Initial model" }) # Add artifacts mesh = v1.create_artifact("mesh", "/assets/hero.fbx") rig = v1.create_artifact("rig", "/assets/hero_rig.fbx") # Set default artifact (for resolve) v1.set_default_artifact("mesh") # Tag the revision v1.tag("approved") # Check tags if v1.has_tag("approved"): print("Revision is approved!") # Get all artifacts for r in v1.get_artifacts(): print(f" {r.name}: {r.location}") # Edge to dependencies texture = kumiho.get_revision("kref://project/tex/skin.texture?r=2") v1.create_edge(texture, kumiho.DEPENDS_ON)
- __init__(pb_revision, client)[source]
Initialize a Revision from a protobuf response.
- Parameters:
pb_revision (
RevisionResponse) – The protobuf RevisionResponse message.client (
_Client) – The client instance for making API calls.
- Return type:
None
- analyze_impact(edge_type_filter=None, max_depth=10, limit=100)[source]
Analyze the impact of changes to this revision.
Returns all revisions that directly or indirectly depend on this revision, sorted by impact depth (closest dependencies first).
- Parameters:
- Returns:
Revisions that would be impacted.
- Return type:
List[ImpactedRevision]
Example
>>> # Before modifying a shared texture >>> impact = texture_v1.analyze_impact() >>> print(f"{len(impact)} revisions would need review") >>> for iv in impact[:5]: ... print(f" {iv.revision_kref} (depth {iv.impact_depth})")
- create_artifact(name, location)[source]
Create a new artifact for this revision.
Artifacts are file references that point to actual assets on disk or network storage. Kumiho tracks the path and metadata but does not upload or copy the files.
- Parameters:
- Returns:
The newly created Artifact object.
- Return type:
Example
>>> mesh = revision.create_artifact("mesh", "/assets/hero.fbx") >>> textures = revision.create_artifact("textures", "smb://server/tex/hero.zip")
- create_edge(target_revision, edge_type, metadata=None)[source]
Create an edge from this revision to another revision.
Edges represent relationships between revisions, such as dependencies, references, or derivations. This is useful for tracking asset lineage.
- Parameters:
target_revision (
Revision) – The target revision to link to.edge_type (
str) – The type of edge. Use constants fromkumiho.EdgeType: -kumiho.DEPENDS_ON: This revision depends on target. -kumiho.DERIVED_FROM: This revision was derived from target. -kumiho.REFERENCED: This revision references target. -kumiho.CONTAINS: This revision contains target.metadata (
Optional[Dict[str,str]]) – Optional metadata for the edge.
- Returns:
The created Edge object.
- Return type:
Example
>>> import kumiho
>>> # Edge to a texture dependency >>> texture = kumiho.get_revision("kref://project/tex/skin.texture?r=2") >>> revision.create_edge(texture, kumiho.DEPENDS_ON)
>>> # Edge with metadata >>> base = kumiho.get_revision("kref://project/models/base.model?r=1") >>> revision.create_edge(base, kumiho.DERIVED_FROM, { ... "modification": "Added details" ... })
- delete(force=False)[source]
Delete this revision.
- Parameters:
force (
bool) – If True, force deletion even if the revision has artifacts. If False (default), deletion may fail.- Raises:
grpc.RpcError – If deletion fails.
- Return type:
Example
>>> revision.delete() # Fails if has artifacts >>> revision.delete(force=True) # Force delete
- delete_attribute(key)[source]
Delete a single metadata attribute.
- Parameters:
key (
str) – The attribute key to delete.- Returns:
True if the attribute was deleted successfully.
- Return type:
Example
>>> revision.delete_attribute("old_field") True
- delete_edge(target_revision, edge_type)[source]
Delete an edge from this revision.
- Parameters:
- Return type:
Example
>>> revision.delete_edge(texture_revision, kumiho.DEPENDS_ON)
- find_path_to(target, edge_type_filter=None, max_depth=10, all_paths=False)[source]
Find the shortest path from this revision to another.
Uses graph traversal to find how two revisions are connected.
- Parameters:
- Return type:
Optional[RevisionPath]- Returns:
RevisionPath if a path exists, None otherwise. Use all_paths=True and access result.paths for multiple paths.
Example
>>> path = model_v1.find_path_to(texture_v3) >>> if path: ... print(f"Path length: {path.total_depth}") ... for step in path.steps: ... print(f" -> {step.revision_kref} via {step.edge_type}")
- get_all_dependencies(edge_type_filter=None, max_depth=10, limit=100)[source]
Get all transitive dependencies of this revision.
Traverses outgoing edges to find all revisions this revision depends on, directly or indirectly.
- Parameters:
- Returns:
Contains all discovered revisions and paths.
- Return type:
TraversalResult
Example
>>> import kumiho
>>> # Get all dependencies up to 5 hops >>> deps = revision.get_all_dependencies( ... edge_type_filter=[kumiho.DEPENDS_ON], ... max_depth=5 ... ) >>> for kref in deps.revision_krefs: ... print(f"Depends on: {kref}")
- get_all_dependents(edge_type_filter=None, max_depth=10, limit=100)[source]
Get all revisions that transitively depend on this revision.
Traverses incoming edges to find all revisions that depend on this revision, directly or indirectly. Useful for impact analysis.
- Parameters:
- Returns:
Contains all dependent revisions.
- Return type:
TraversalResult
Example
>>> # Find everything that would be affected by changing this texture >>> dependents = texture_v1.get_all_dependents([kumiho.DEPENDS_ON]) >>> print(f"{len(dependents.revision_krefs)} revisions would be affected")
- get_artifact(name)[source]
Get a specific artifact by name from this revision.
- Parameters:
name (
str) – The name of the artifact.- Returns:
The Artifact object.
- Return type:
- Raises:
grpc.RpcError – If the artifact is not found.
Example
>>> mesh = revision.get_artifact("mesh") >>> print(mesh.location)
- get_artifacts()[source]
Get all artifacts associated with this revision.
- Returns:
A list of Artifact objects.
- Return type:
List[Artifact]
Example
>>> for artifact in revision.get_artifacts(): ... print(f"{artifact.name}: {artifact.location}")
- get_attribute(key)[source]
Get a single metadata attribute.
- Parameters:
key (
str) – The attribute key to retrieve.- Return type:
- Returns:
The attribute value if it exists, None otherwise.
Example
>>> revision.get_attribute("render_engine") "cycles"
- get_edges(edge_type_filter=None, direction=0)[source]
Get edges involving this revision.
- Parameters:
- Returns:
A list of Edge objects.
- Return type:
List[Edge]
Example
>>> import kumiho
>>> # Get all dependencies >>> deps = revision.get_edges(kumiho.DEPENDS_ON, kumiho.OUTGOING)
>>> # Get all revisions that depend on this one >>> dependents = revision.get_edges(kumiho.DEPENDS_ON, kumiho.INCOMING)
- get_item()[source]
Get the parent item of this revision.
- Returns:
The Item object that contains this revision.
- Return type:
Example
>>> item = revision.get_item() >>> print(item.item_name)
- get_locations()[source]
Get the file locations of all artifacts in this revision.
This is a convenience method to quickly get all file paths.
- Returns:
A list of file location strings.
- Return type:
List[str]
Example
>>> locations = revision.get_locations() >>> for loc in locations: ... print(loc)
- get_project()[source]
Get the project that contains this revision.
- Returns:
The Project object.
- Return type:
Example
>>> project = revision.get_project() >>> print(project.name)
- get_space()[source]
Get the space that contains this revision’s item.
- Returns:
The Space object.
- Return type:
Example
>>> space = revision.get_space() >>> print(space.path)
- has_tag(tag)[source]
Check if this revision currently has a specific tag.
This makes a server call to ensure the tag status is current.
- Parameters:
tag (
str) – The tag to check for.- Returns:
True if the revision has the tag, False otherwise.
- Return type:
Example
>>> if revision.has_tag("approved"): ... print("Ready for production!")
- refresh()[source]
Refresh this revision’s data from the server.
This updates all properties to reflect the current state in the database, including tags that may have changed (like “latest”).
Example
>>> revision.refresh() >>> print(revision.tags) # Now shows current tags
- Return type:
- set_attribute(key, value)[source]
Set a single metadata attribute.
This allows granular updates to metadata without replacing the entire metadata map.
- Parameters:
- Returns:
True if the attribute was set successfully.
- Return type:
Example
>>> revision.set_attribute("render_engine", "cycles") True
- set_default_artifact(artifact_name)[source]
Set the default artifact for this revision.
The default artifact is used when resolving the revision’s kref without specifying an artifact name.
Example
>>> revision.set_default_artifact("mesh") >>> # Now kref://project/model.kind?r=1 resolves to the mesh
- set_deprecated(status)[source]
Set the deprecated status of this revision.
Deprecated revisions are hidden from default queries but remain accessible for historical reference.
Example
>>> revision.set_deprecated(True) # Hide from queries
- set_metadata(metadata)[source]
Set or update metadata for this revision.
Metadata is merged with existing metadata—existing keys are overwritten and new keys are added.
- Parameters:
metadata (
Dict[str,str]) – Dictionary of metadata key-value pairs.- Returns:
The updated Revision object.
- Return type:
Example
>>> revision.set_metadata({ ... "render_engine": "arnold", ... "frame_range": "1-100", ... "resolution": "4K" ... })
- tag(tag)[source]
Apply a tag to this revision.
Tags are used to categorize revisions and mark their status. Common tags include “latest”, “published”, “approved”, etc.
Note
The “latest” tag is automatically managed—it always points to the newest revision.
Example
>>> revision.tag("approved") >>> revision.tag("ready-for-lighting")
- property tags: List[str]
Get the current tags for this revision.
This property automatically refreshes from the server if the data might be stale (older than 5 seconds), ensuring dynamic tags like “latest” are always current.
- Returns:
The list of tags on this revision.
- Return type:
List[str]
Example
>>> revision = item.get_revision(1) >>> print(revision.tags) # ['latest', 'approved']
- was_tagged(tag)[source]
Check if this revision was ever tagged with a specific tag.
This checks the historical record, not just current tags.
- Parameters:
tag (
str) – The tag to check for.- Returns:
True if the revision was ever tagged with this tag.
- Return type:
Example
>>> if revision.was_tagged("approved"): ... print("Was approved at some point")
- class kumiho.Artifact[source]
Bases:
KumihoObjectA file reference within a revision in the Kumiho system.
Artifacts are the leaf nodes of the Kumiho hierarchy. They point to actual files on local disk, network storage, or cloud URIs. Kumiho tracks the path and metadata but does not upload or modify the files.
The artifact’s kref includes both revision and artifact name:
kref://project/space/item.kind?r=1&a=artifact_nameExample
Working with artifacts:
import kumiho revision = kumiho.get_revision("kref://project/models/hero.model?r=1") # Create artifacts mesh = revision.create_artifact("mesh", "/assets/hero.fbx") rig = revision.create_artifact("rig", "/assets/hero_rig.fbx") textures = revision.create_artifact("textures", "smb://server/tex/hero/") # Set metadata mesh.set_metadata({ "triangles": "2.5M", "format": "FBX 2020", "units": "centimeters" }) # Set as default artifact mesh.set_default() # Get artifact by name retrieved = revision.get_artifact("mesh") print(f"Location: {retrieved.location}") # Navigate hierarchy item = mesh.get_item() project = mesh.get_project()
- __init__(pb_artifact, client)[source]
Initialize an Artifact from a protobuf response.
- Parameters:
pb_artifact (
ArtifactResponse) – The protobuf ArtifactResponse message.client (
_Client) – The client instance for making API calls.
- Return type:
None
- delete(force=False)[source]
Delete this artifact.
- Parameters:
force (
bool) – If True, force deletion. If False (default), normal deletion rules apply.- Raises:
grpc.RpcError – If deletion fails.
- Return type:
Example
>>> artifact.delete()
- delete_attribute(key)[source]
Delete a single metadata attribute.
- Parameters:
key (
str) – The attribute key to delete.- Returns:
True if the attribute was deleted successfully.
- Return type:
Example
>>> artifact.delete_attribute("old_field") True
- get_attribute(key)[source]
Get a single metadata attribute.
- Parameters:
key (
str) – The attribute key to retrieve.- Return type:
- Returns:
The attribute value if it exists, None otherwise.
Example
>>> artifact.get_attribute("file_size") "125MB"
- get_item()[source]
Get the item that contains this artifact.
- Returns:
The Item object.
- Return type:
Example
>>> item = artifact.get_item() >>> print(item.item_name)
- get_project()[source]
Get the project containing this artifact.
- Returns:
The Project object.
- Return type:
Example
>>> project = artifact.get_project() >>> print(project.name)
- get_revision()[source]
Get the parent revision of this artifact.
- Returns:
The Revision object that contains this artifact.
- Return type:
Example
>>> revision = artifact.get_revision() >>> print(f"Revision {revision.number}")
- get_space()[source]
Get the space containing this artifact’s item.
- Returns:
The Space object.
- Return type:
Example
>>> space = artifact.get_space() >>> print(space.path)
- property name: str
Get the artifact name from its kref.
- Returns:
The artifact name extracted from the kref URI.
- Return type:
Example
>>> artifact = revision.get_artifact("mesh") >>> print(artifact.name) # "mesh"
- set_attribute(key, value)[source]
Set a single metadata attribute.
This allows granular updates to metadata without replacing the entire metadata map.
- Parameters:
- Returns:
True if the attribute was set successfully.
- Return type:
Example
>>> artifact.set_attribute("file_size", "125MB") True
- set_default()[source]
Set this artifact as the default for its revision.
The default artifact is used when resolving the revision’s kref without specifying an artifact name.
Example
>>> mesh = revision.create_artifact("mesh", "/assets/model.fbx") >>> mesh.set_default() >>> # Now resolving the revision kref returns this artifact's location
- Return type:
- set_deprecated(status)[source]
Set the deprecated status of this artifact.
Deprecated artifacts are hidden from default queries but remain accessible for historical reference.
Example
>>> artifact.set_deprecated(True) # Hide from queries >>> artifact.set_deprecated(False) # Restore visibility
- set_metadata(metadata)[source]
Set or update metadata for this artifact.
Metadata is merged with existing metadata—existing keys are overwritten and new keys are added.
- Parameters:
metadata (
Dict[str,str]) – Dictionary of metadata key-value pairs.- Returns:
The updated Artifact object.
- Return type:
Example
>>> artifact.set_metadata({ ... "file_size": "125MB", ... "format": "FBX 2020", ... "triangles": "2.5M", ... "software": "Maya 2024" ... })
- class kumiho.Edge[source]
Bases:
KumihoObjectA relationship between two revisions in the Kumiho system.
Edges represent semantic relationships between revisions, enabling dependency tracking, lineage visualization, and impact analysis. They are directional (source -> target) and typed.
- Common use cases:
Track which textures a model uses (DEPENDS_ON)
Record that a LOD was created from a high-poly model (DERIVED_FROM)
Link a render to the scene file that created it (CREATED_FROM)
Example:
import kumiho # Get revisions model = kumiho.get_revision("kref://project/models/hero.model?r=1") texture = kumiho.get_revision("kref://project/tex/skin.texture?r=2") # Create edge with metadata edge = model.create_edge(texture, kumiho.DEPENDS_ON, { "channel": "diffuse", "uv_set": "0" }) # Inspect edge print(f"Type: {edge.edge_type}") print(f"From: {edge.source_kref}") print(f"To: {edge.target_kref}") # Delete edge edge.delete()
- class kumiho.Kref[source]
Bases:
strA Kumiho Artifact Reference (URI-based unique identifier).
Kref is a subclass of
str, so it behaves like a string but provides utility methods for parsing and extracting components from the URI.The kref format is:
kref://project/space/item.kind?r=REVISION&a=ARTIFACT
Example:
from kumiho import Kref # Create from string kref = Kref("kref://my-project/assets/hero.model?r=2") # Use as string (since Kref extends str) print(kref) # kref://my-project/assets/hero.model?r=2 # Parse components print(kref.get_space()) # "my-project/assets" print(kref.get_revision()) # 2 # Compare with strings if kref == "kref://my-project/assets/hero.model?r=2": print("Match!")
Note
Since Kref is a string subclass, you can use it anywhere a string is expected. All string methods work normally.
- static __new__(cls, uri, *, validate=True)[source]
Create a new Kref instance.
- Parameters:
- Returns:
A Kref instance that is also a string.
- Return type:
- Raises:
KrefValidationError – If validate=True and the URI is invalid.
Example
>>> kref = Kref("kref://project/space/item.kind?r=1") >>> isinstance(kref, str) True
- classmethod from_pb(pb_kref)[source]
Create a Kref from a protobuf message.
This is used for krefs received from the server, which are trusted.
- Parameters:
pb_kref (
Kref) – The protobuf Kref message.- Returns:
A Kref instance.
- Return type:
- get_artifact_name()[source]
Extract the artifact name from the URI query string.
- Returns:
The artifact name if present, None otherwise.
- Return type:
Optional[str]
Example
>>> Kref("kref://project/models/hero.model?r=1&a=mesh").get_artifact_name() 'mesh' >>> Kref("kref://project/models/hero.model?r=1").get_artifact_name() None
- get_item_name()[source]
Extract the item name with kind from the URI.
- Returns:
The item name including kind (e.g., “hero.model”).
- Return type:
Example
>>> Kref("kref://project/models/hero.model").get_item_name() 'hero.model'
- get_kind()[source]
Extract the item kind from the URI.
- Returns:
The item kind (e.g., “model”, “texture”).
- Return type:
Example
>>> Kref("kref://project/models/hero.model").get_kind() 'model'
- get_path()[source]
Extract the path component from the URI.
Returns the part after
kref://and before any query parameters.- Returns:
The path (e.g., “project/space/item.kind”).
- Return type:
Example
>>> Kref("kref://project/models/hero.model?r=1").get_path() 'project/models/hero.model'
- get_revision()[source]
Extract the revision number from the URI query string.
- Returns:
The revision number, or 1 if not specified.
- Return type:
Example
>>> Kref("kref://project/models/hero.model?r=3").get_revision() 3 >>> Kref("kref://project/models/hero.model").get_revision() 1
- get_space()[source]
Extract the space path from the URI.
Returns the path up to but not including the item name.
- Returns:
The space path (e.g., “project/models”).
- Return type:
Example
>>> Kref("kref://project/models/hero.model").get_space() 'project/models'
- class kumiho.Event[source]
Bases:
objectA real-time notification from the Kumiho server.
Events represent changes or actions that occurred in the Kumiho system, such as creating versions, applying tags, or deleting resources. Use
kumiho.event_stream()to subscribe to events.- routing_key
The event type identifier (e.g., “version.created”, “version.tagged”). Use wildcards in filters to match patterns.
- Type:
- details
Additional event-specific information (e.g., tag name for tagged events).
- cursor
Opaque cursor for resumable streaming. Save this value and pass it to
event_stream(cursor=...)to resume from this event after reconnection. Only available on Creator tier and above.- Type:
Optional[str]
Example:
import kumiho # React to revision creation with cursor tracking last_cursor = None try: for event in kumiho.event_stream( routing_key_filter="revision.created", cursor=last_cursor ): revision = kumiho.get_revision(event.kref) print(f"New revision: {revision.item_kref} v{revision.number}") print(f" Created by: {event.author}") print(f" At: {event.timestamp}") last_cursor = event.cursor # Save cursor except ConnectionError: # Reconnect using saved cursor pass # Filter by kref pattern for event in kumiho.event_stream( routing_key_filter="*", kref_filter="kref://production-project/**" ): print(f"Production change: {event.routing_key}")
- exception kumiho.ProjectLimitError[source]
Bases:
ExceptionRaised when guardrails block project creation (e.g., max projects reached).
- class kumiho.Bundle[source]
Bases:
ItemA special item type that aggregates other items.
Bundles provide a way to group related items together. Unlike regular items, bundles cannot be created using the standard
create_itemmethod—thebundlekind is reserved.Use
Project.create_bundle()orSpace.create_bundle()to create bundles.- Key features:
Aggregates items (not revisions) via
COLLECTSrelationships.Each membership change creates a new revision for audit trail.
Revision metadata is immutable, providing complete history.
Cannot contain itself (self-reference protection).
Example:
import kumiho # Create a bundle from a project project = kumiho.get_project("film-2024") bundle = project.create_bundle("release-v1") # Add items model = kumiho.get_item("kref://film-2024/models/hero.model") texture = kumiho.get_item("kref://film-2024/textures/hero.texture") bundle.add_member(model) bundle.add_member(texture) # List current members for member in bundle.get_members(): print(f"{member.item_kref} added by {member.added_by_username}") # Remove a member bundle.remove_member(model) # View complete audit history for entry in bundle.get_history(): print(f"v{entry.revision_number}: {entry.action}")
See also
Project.create_bundle(): Create a bundle in a project.Space.create_bundle(): Create a bundle in a space.BundleMember: Data class for member information.BundleRevisionHistory: Data class for audit entries.- __init__(pb, client)[source]
Initialize a Bundle from a protobuf response.
- Parameters:
pb (
ItemResponse) – The ItemResponse protobuf message.client (
_Client) – The client instance for making subsequent calls.
- Raises:
ValueError – If the kind is not ‘bundle’.
- Return type:
None
- add_member(member, metadata=None)[source]
Add an item to this bundle.
Creates a new revision of the bundle to track the change. The revision metadata will include the action (
"ADDED") and the member item kref for audit purposes.- Parameters:
- Returns:
- A tuple containing:
success: Whether the operation succeeded.
message: A status message.
new_revision: The new bundle revision created for this change.
- Return type:
- Raises:
ValueError – If trying to add the bundle to itself.
grpc.RpcError – If the member is already in the bundle (status code
ALREADY_EXISTS).
Example:
hero_model = kumiho.get_item("kref://project/models/hero.model") # Add with optional metadata success, msg, revision = bundle.add_member( hero_model, metadata={"reason": "character bundle", "approved_by": "director"} ) if success: print(f"Added in revision {revision.number}")
- get_history()[source]
Get the full history of membership changes.
Returns all revisions with their associated actions, providing a complete and immutable audit trail of all adds and removes.
The history is ordered by revision number, starting with the initial
"CREATED"action.- Returns:
- List of history entries, ordered
by revision number (oldest first).
- Return type:
List[BundleRevisionHistory]
Example:
history = bundle.get_history() for entry in history: print(f"Revision {entry.revision_number}:") print(f" Action: {entry.action}") print(f" By: {entry.username}") print(f" At: {entry.created_at}") if entry.member_item_kref: print(f" Item: {entry.member_item_kref}")
- get_members(revision_number=None)[source]
Get all items that are members of this bundle.
Returns information about each member item, including when it was added and by whom.
- Parameters:
revision_number (
Optional[int]) – Optional specific revision to query. If not provided, returns current membership.- Returns:
List of member information objects.
- Return type:
List[BundleMember]
Example:
# Get current members members = bundle.get_members() for member in members: print(f"{member.item_kref}") print(f" Added by: {member.added_by_username}") print(f" In revision: {member.added_in_revision}") # Get empty list if no members if not members: print("Bundle is empty")
- remove_member(member, metadata=None)[source]
Remove an item from this bundle.
Creates a new revision of the bundle to track the change. The revision metadata will include the action (
"REMOVED") and the member item kref for audit purposes.- Parameters:
- Returns:
- A tuple containing:
success: Whether the operation succeeded.
message: A status message.
new_revision: The new bundle revision created for this change.
- Return type:
- Raises:
grpc.RpcError – If the member is not in the bundle (status code
NOT_FOUND).
Example:
# Remove an item from the bundle success, msg, revision = bundle.remove_member(hero_model) if success: print(f"Removed in revision {revision.number}")
- class kumiho.BundleMember[source]
Bases:
objectAn item that is a member of a bundle.
Represents the membership relationship between an item and a bundle, including metadata about when and by whom the item was added.
Example:
members = bundle.get_members() for member in members: print(f"Item: {member.item_kref}") print(f"Added by: {member.added_by_username}") print(f"Added at: {member.added_at}") print(f"In revision: {member.added_in_revision}")
- __init__(item_kref, added_at, added_by, added_by_username, added_in_revision)
- class kumiho.BundleRevisionHistory[source]
Bases:
objectA historical change to a bundle’s membership.
Each entry captures a single add or remove operation, providing an immutable audit trail of all membership changes. The metadata is immutable once created, ensuring complete traceability.
- member_item_kref
The item that was added/removed. None for the initial
"CREATED"action.- Type:
Optional[Kref]
Example:
history = bundle.get_history() for entry in history: print(f"Revision {entry.revision_number}: {entry.action}") if entry.member_item_kref: print(f" Item: {entry.member_item_kref}") print(f" By: {entry.username} at {entry.created_at}")
- __init__(revision_number, action, member_item_kref, author, username, created_at, metadata)
- exception kumiho.ReservedKindError[source]
Bases:
ExceptionRaised when attempting to create an item with a reserved kind.
This error is raised when calling
Space.create_item()or the low-level clientcreate_itemwith a reserved kind such asbundle.Example:
import kumiho space = project.get_space("assets") # This will raise ReservedKindError try: space.create_item("my-bundle", "bundle") except kumiho.ReservedKindError as e: print(f"Error: {e}") # Use create_bundle instead bundle = space.create_bundle("my-bundle")
- exception kumiho.KrefValidationError[source]
Bases:
ValueErrorRaised when a Kref URI is invalid or contains malicious patterns.
- exception kumiho.EdgeTypeValidationError[source]
Bases:
ValueErrorRaised when an edge type is invalid or potentially malicious.
- kumiho.validate_kref(uri)[source]
Validate a Kref URI for security and correctness.
Checks for: - Proper kref:// scheme - No path traversal patterns (..) - No control characters - Valid path segment format
- Parameters:
uri (
str) – The kref URI to validate.- Raises:
KrefValidationError – If the URI is invalid or contains malicious patterns.
- Return type:
Example:
from kumiho.kref import validate_kref, KrefValidationError try: validate_kref("kref://project/space/item.kind?r=1") except KrefValidationError as e: print(f"Invalid kref: {e}")
- kumiho.validate_edge_type(edge_type)[source]
Validate an edge type for security and correctness.
Edge types must: - Start with an uppercase letter - Contain only uppercase letters, digits, and underscores - Be 1-50 characters long
- Parameters:
edge_type (
str) – The edge type to validate.- Raises:
EdgeTypeValidationError – If the edge type is invalid.
- Return type:
Example:
from kumiho.edge import validate_edge_type, EdgeTypeValidationError try: validate_edge_type("DEPENDS_ON") # OK validate_edge_type("depends_on") # Raises error except EdgeTypeValidationError as e: print(f"Invalid edge type: {e}")
- kumiho.is_valid_kref(uri)[source]
Check if a Kref URI is valid without raising exceptions.
- Parameters:
uri (
str) – The kref URI to validate.- Return type:
- Returns:
True if the URI is valid, False otherwise.
Example:
from kumiho.kref import is_valid_kref if is_valid_kref("kref://project/space/item.kind"): print("Valid!")
- kumiho.is_valid_edge_type(edge_type)[source]
Check if an edge type is valid without raising exceptions.
- kumiho.connect(endpoint=None, token=None, *, enable_auto_login=True, use_discovery=None, default_metadata=None, tenant_hint=None)[source]
Create a new Kumiho client with explicit configuration.
Use this when you need more control over the client configuration, such as connecting to a specific server or using a custom token.
- Parameters:
endpoint (
Optional[str]) – The gRPC server endpoint (e.g., “localhost:50051” or “https://us-central.kumiho.cloud”).token (
Optional[str]) – The authentication token. If not provided and enable_auto_login is True, attempts to load from cache.enable_auto_login (
bool) – If True, automatically use cached credentials when no token is provided.use_discovery (
Optional[bool]) – If True, use the discovery service to find the regional server. If None, auto-detect.default_metadata (
Optional[List[Tuple[str,str]]]) – Additional gRPC metadata to include with all requests (e.g., custom headers).tenant_hint (
Optional[str]) – Tenant slug or ID for multi-tenant routing.
- Returns:
A configured client instance.
- Return type:
_Client
Example
Connect to a local development server:
client = kumiho.connect( endpoint="localhost:50051", token=None # No auth for local dev )
Connect to production with explicit token:
client = kumiho.connect( endpoint="https://us-central.kumiho.cloud", token=os.environ["KUMIHO_TOKEN"] )
Use with context manager for temporary switching:
client = kumiho.connect(endpoint="localhost:50051") with kumiho.use_client(client): local_projects = kumiho.get_projects()
See also
auto_configure_from_discovery(): Recommended for production.configure_default_client(): Set as global default.
- class kumiho.use_client[source]
Bases:
objectContext manager to temporarily set the current client instance.
This is useful for multi-tenant scenarios or when you need to use different clients for different operations within the same thread or async context.
- Parameters:
client (
_Client) – The client to use within the context.
Example
Using different clients for different tenants:
import kumiho tenant_a_client = kumiho.connect(endpoint="tenant-a.kumiho.cloud:443") tenant_b_client = kumiho.connect(endpoint="tenant-b.kumiho.cloud:443") with kumiho.use_client(tenant_a_client): # All operations here use tenant_a_client projects_a = kumiho.get_projects() with kumiho.use_client(tenant_b_client): # All operations here use tenant_b_client projects_b = kumiho.get_projects()
Note
Context-local clients take precedence over the global default. This works correctly with async code and concurrent requests.
- kumiho.get_client()[source]
Get the current Kumiho client instance.
Returns the context-local client if set via
use_client, otherwise returns the global default client (creating one if needed).- Returns:
The active client instance.
- Return type:
_Client
- Raises:
RuntimeError – If no client is configured and auto-bootstrap fails.
Example
>>> client = kumiho.get_client() >>> projects = client.get_projects()
- kumiho.configure_default_client(client)[source]
Set the global default client used by top-level helper functions.
This function allows you to manually configure the default client that will be used by functions like
create_project(),get_projects(), etc.- Parameters:
client (
_Client) – The client instance to set as the default.- Returns:
The same client instance (for chaining).
- Return type:
_Client
Example
>>> client = kumiho.connect(endpoint="localhost:50051") >>> kumiho.configure_default_client(client) >>> # Now all top-level functions use this client >>> projects = kumiho.get_projects()
- kumiho.auto_configure_from_discovery(*, tenant_hint=None, force_refresh=False, interactive=False)[source]
Configure the default client using cached credentials and discovery.
This is the recommended way to bootstrap the Kumiho client. It uses credentials cached by
kumiho-auth loginand calls the control-plane discovery endpoint to resolve the correct regional server.- Parameters:
tenant_hint (
Optional[str]) – Optional tenant slug or ID to use for discovery. If not provided, the user’s default tenant is used.force_refresh (
bool) – If True, bypass the discovery cache and fetch fresh routing information from the control plane.interactive (
bool) – If True and no cached credentials exist, prompt for interactive login. Defaults to False for script safety.
- Returns:
The configured client instance, also set as the default.
- Return type:
_Client
- Raises:
RuntimeError – If no cached credentials exist and interactive mode is disabled.
Example
Basic usage:
import kumiho # First, run: kumiho-auth login # Then in your code: kumiho.auto_configure_from_discovery() # Now you can use all kumiho functions projects = kumiho.get_projects()
With tenant hint for multi-tenant access:
kumiho.auto_configure_from_discovery(tenant_hint="my-studio")
See also
connect(): For manual client configuration.use_client: For temporary client switching.
- kumiho.LATEST_TAG = 'latest'
Standard tag name indicating the latest revision of an item.
- Type:
- kumiho.PUBLISHED_TAG = 'published'
Standard tag name indicating a published/released revision.
- Type:
- class kumiho.EdgeType[source]
Bases:
objectStandard edge types for Kumiho relationships.
These constants define the semantic meaning of relationships between revisions. Use them when creating or querying edges.
All edge types use UPPERCASE format as required by the Neo4j graph database.
Example:
import kumiho # Model depends on texture model_v1.create_edge(texture_v2, kumiho.DEPENDS_ON) # LOD derived from high-poly lod_v1.create_edge(highpoly_v1, kumiho.DERIVED_FROM)
- BELONGS_TO = 'BELONGS_TO'
Ownership or grouping relationship.
- CONTAINS = 'CONTAINS'
Source contains or includes target.
- CREATED_FROM = 'CREATED_FROM'
Source was generated/created from target.
- DEPENDS_ON = 'DEPENDS_ON'
Source requires target to function.
- DERIVED_FROM = 'DERIVED_FROM'
Source was derived or modified from target.
- REFERENCED = 'REFERENCED'
Soft reference relationship.
- class kumiho.EdgeDirection[source]
Bases:
objectDirection constants for edge traversal queries.
When querying edges, you can specify which direction to traverse: outgoing edges (from source), incoming edges (to target), or both.
Example:
import kumiho # Get dependencies (what this revision depends on) deps = revision.get_edges(kumiho.DEPENDS_ON, kumiho.OUTGOING) # Get dependents (what depends on this revision) dependents = revision.get_edges(kumiho.DEPENDS_ON, kumiho.INCOMING) # Get all relationships all_edges = revision.get_edges(direction=kumiho.BOTH)
- BOTH = 2
Edges in either direction.
- INCOMING = 1
Edges where the queried revision is the target.
- OUTGOING = 0
Edges where the queried revision is the source.
- kumiho.get_tenant_info(tenant_hint=None)[source]
Get information about the current tenant from the discovery cache.
This retrieves tenant information that was cached during discovery, including the tenant ID, tenant name/slug, roles, and region info.
- Parameters:
tenant_hint (
Optional[str]) – Optional tenant slug or ID to look up. If not provided, looks up the default tenant (using “_default” cache key).- Returns:
- A dictionary containing tenant info:
tenant_id: The unique tenant identifier
tenant_name: The tenant slug/name (human-readable identifier)
roles: List of roles the user has in this tenant
region: Region routing information
Returns None if no cached info is available.
- Return type:
Optional[Dict[str, Any]]
Example
>>> info = kumiho.get_tenant_info() >>> if info: ... print(f"Tenant: {info['tenant_name']}") ... print(f"Roles: {info['roles']}") Tenant: kumihoclouds Roles: ['owner', 'admin']
Note
This requires that discovery has been performed (either via
auto_configure_from_discovery()orKUMIHO_AUTO_CONFIGURE=1). If using direct connection without discovery, this will return None.
- kumiho.get_tenant_slug(tenant_hint=None)[source]
Get the tenant slug/name for use in project naming.
This is a convenience function that returns just the tenant name/slug, which is useful for constructing project names like “ComfyUI@{tenant_slug}”.
If tenant_name contains spaces or special characters, returns a shortened tenant_id instead (first 8 characters of the UUID).
- Parameters:
tenant_hint (
Optional[str]) – Optional tenant slug or ID to look up.- Returns:
The tenant slug/name, or None if not available.
- Return type:
Optional[str]
Example
>>> slug = kumiho.get_tenant_slug() >>> project_name = f"ComfyUI@{slug or 'default'}" >>> print(project_name) ComfyUI@kumihoclouds
- kumiho.create_project(name, description='')[source]
Create a new project.
Projects are the top-level containers for all assets. Each project has its own namespace for spaces and items.
- Parameters:
- Returns:
The newly created Project object.
- Return type:
- Raises:
ProjectLimitError – If the tenant has reached their project limit.
KumihoError – If the project name is invalid or already exists.
Example
>>> project = kumiho.create_project( ... "commercial-2024", ... "Assets for 2024 commercial campaign" ... ) >>> print(project.name) commercial-2024
- kumiho.get_projects()[source]
List all projects accessible to the current user.
- Returns:
A list of Project objects.
- Return type:
List[Project]
Example
>>> projects = kumiho.get_projects() >>> for p in projects: ... print(f"{p.name}: {p.description}") commercial-2024: Assets for 2024 commercial campaign film-project: Feature film VFX assets
- kumiho.get_project(name)[source]
Get a project by name.
- Parameters:
name (
str) – The name of the project to retrieve.- Returns:
The Project object if found, None otherwise.
- Return type:
Optional[Project]
Example
>>> project = kumiho.get_project("commercial-2024") >>> if project: ... spaces = project.get_spaces()
- kumiho.delete_project(project_id, force=False)[source]
Delete a project.
- Parameters:
- Returns:
A StatusResponse indicating success or failure.
- Return type:
StatusResponse
Warning
Force deletion is irreversible and removes all spaces, items, revisions, artifacts, and edges within the project.
Example
>>> # Soft delete (deprecate) >>> kumiho.delete_project("proj-uuid-here")
>>> # Hard delete (permanent) >>> kumiho.delete_project("proj-uuid-here", force=True)
- kumiho.item_search(context_filter='', name_filter='', kind_filter='')[source]
Search for items across projects and spaces.
- Parameters:
- Returns:
A list of Item objects matching the filters.
- Return type:
List[Item]
Example
>>> # Find all models in any project >>> models = kumiho.item_search(kind_filter="model")
>>> # Find character assets in a specific project >>> chars = kumiho.item_search( ... context_filter="film-project/characters", ... kind_filter="model" ... )
>>> # Wildcard search >>> heroes = kumiho.item_search(name_filter="hero*")
- kumiho.get_item(kref)[source]
Get an item by its kref URI.
- Parameters:
kref (
str) – The kref URI of the item (e.g., “kref://project/space/item.kind”).- Returns:
The Item object.
- Return type:
- Raises:
grpc.RpcError – If the item is not found.
Example
>>> item = kumiho.get_item( ... "kref://film-project/characters/hero.model" ... ) >>> revisions = item.get_revisions()
- kumiho.get_bundle(kref)[source]
Get a bundle by its kref URI.
This is a convenience function that gets an item and verifies it’s a bundle.
- Parameters:
kref (
str) – The kref URI of the bundle item (e.g., “kref://project/space/bundle-name.bundle”).- Returns:
The Bundle object.
- Return type:
- Raises:
ValueError – If the item exists but is not a bundle.
grpc.RpcError – If the bundle is not found.
Example
>>> bundle = kumiho.get_bundle( ... "kref://film-project/shots/shot001.bundle" ... ) >>> members = bundle.get_members()
- kumiho.get_revision(kref)[source]
Get a revision by its kref URI.
- Parameters:
kref (
str) – The kref URI of the revision (e.g., “kref://project/space/item.kind?r=1”).- Returns:
The Revision object.
- Return type:
- Raises:
grpc.RpcError – If the revision is not found.
Example
>>> revision = kumiho.get_revision( ... "kref://film-project/characters/hero.model?r=3" ... ) >>> artifacts = revision.get_artifacts() >>> for a in artifacts: ... print(a.location)
- kumiho.get_artifact(kref)[source]
Get an artifact by its kref URI.
- Parameters:
kref (
str) – The kref URI of the artifact (e.g., “kref://project/space/item.kind?r=1&a=main”).- Returns:
The Artifact object.
- Return type:
- Raises:
grpc.RpcError – If the artifact is not found.
ValueError – If the kref is missing the artifact name (&a=).
Example
>>> artifact = kumiho.get_artifact( ... "kref://film-project/characters/hero.model?r=3&a=mesh" ... ) >>> print(artifact.location) /projects/film/char/hero_v3.fbx
- kumiho.get_artifacts_by_location(location)[source]
Find all artifacts at a specific file location.
This is useful for reverse lookups—finding which Kumiho artifacts reference a particular file path.
- Parameters:
location (
str) – The file path or URI to search for.- Returns:
A list of Artifact objects at that location.
- Return type:
List[Artifact]
Example
>>> artifacts = kumiho.get_artifacts_by_location( ... "/shared/assets/hero_v3.fbx" ... ) >>> for a in artifacts: ... print(f"{a.kref} -> {a.location}")
- kumiho.set_attribute(kref, key, value)[source]
Set a single metadata attribute on any entity.
This allows granular updates to metadata without replacing the entire metadata map. Works on any entity type (Revision, Item, Artifact, or Space) identified by kref.
- Parameters:
- Returns:
True if the attribute was set successfully.
- Return type:
- Raises:
grpc.RpcError – If the entity is not found or the key is reserved.
Example
>>> kumiho.set_attribute( ... "kref://project/models/hero.model?r=1", ... "render_engine", ... "cycles" ... ) True
- kumiho.get_attribute(kref, key)[source]
Get a single metadata attribute from any entity.
- Parameters:
- Return type:
- Returns:
The attribute value if it exists, None otherwise.
Example
>>> kumiho.get_attribute( ... "kref://project/models/hero.model?r=1", ... "render_engine" ... ) "cycles"
- kumiho.delete_attribute(kref, key)[source]
Delete a single metadata attribute from any entity.
- Parameters:
- Returns:
True if the attribute was deleted successfully.
- Return type:
- Raises:
grpc.RpcError – If the entity is not found or the key is reserved.
Example
>>> kumiho.delete_attribute( ... "kref://project/models/hero.model?r=1", ... "old_field" ... ) True
- kumiho.event_stream(routing_key_filter='', kref_filter='', cursor=None, consumer_group=None, from_beginning=False)[source]
Subscribe to real-time events from the Kumiho server.
Events are streamed as they occur, allowing you to react to changes in the database such as new revisions, tag changes, or deletions.
- Parameters:
routing_key_filter (
str) – Filter events by routing key pattern. Supports wildcards (e.g.,item.model.*,revision.#).kref_filter (
str) – Filter events by kref pattern. Supports glob patterns (e.g.,kref://projectA/**/*.model).cursor (
Optional[str]) – Resume from a previous cursor position (Creator tier+). Pass the cursor from the last received event to continue from that point after reconnection.consumer_group (
Optional[str]) – Consumer group name for load-balanced delivery (Enterprise tier only). Multiple consumers in the same group each receive different events.from_beginning (
bool) – Start from earliest available events instead of live-only (Creator tier+, subject to retention limits).
- Yields:
Event –
- Event objects as they occur. Each event includes a
cursor field that can be saved for resumption.
- Event objects as they occur. Each event includes a
- Return type:
Example
>>> # Watch for all revision events with cursor tracking >>> last_cursor = None >>> try: ... for event in kumiho.event_stream( ... routing_key_filter="revision.*", ... kref_filter="kref://film-project/**", ... cursor=last_cursor ... ): ... print(f"{event.routing_key}: {event.kref}") ... last_cursor = event.cursor # Save for reconnection ... if event.routing_key == "revision.tagged": ... print(f" Tag: {event.details.get('tag')}") ... except ConnectionError: ... # Reconnect using saved cursor ... pass
Note
This is a blocking iterator. Use in a separate thread or async context for production applications.
Cursor-based resume requires Creator tier or above. Use
get_event_capabilities()to check your tier’s capabilities.
- kumiho.get_event_capabilities()[source]
Get event streaming capabilities for the current tenant tier.
Returns the capabilities available based on the authenticated tenant’s subscription tier. Use this to determine which features (cursor resume, consumer groups, replay) are available before using them.
- Returns:
- Object with capability flags and limits:
supports_replay: Can replay past events
supports_cursor: Can resume from cursor
supports_consumer_groups: Can use consumer groups (Enterprise)
max_retention_hours: Event retention period (-1 = unlimited)
max_buffer_size: Max events in buffer (-1 = unlimited)
tier: Tier name (free, creator, studio, enterprise)
- Return type:
Example
>>> caps = kumiho.get_event_capabilities() >>> print(f"Tier: {caps.tier}") Tier: creator >>> if caps.supports_cursor: ... # Use cursor-based streaming ... last_cursor = load_saved_cursor() ... for event in kumiho.event_stream(cursor=last_cursor): ... process(event) ... save_cursor(event.cursor) ... else: ... # Free tier - no cursor support ... for event in kumiho.event_stream(): ... process(event)
- class kumiho.EventCapabilities[source]
Bases:
objectEvent streaming capabilities for the current tenant tier.
- supports_replay
Whether this tier supports replaying past events.
- supports_cursor
Whether cursor-based resume is supported.
- supports_consumer_groups
Whether consumer groups are supported (Enterprise only).
- max_retention_hours
Maximum event retention in hours (0 = none, -1 = unlimited).
- max_buffer_size
Maximum events in buffer (0 = none, -1 = unlimited).
- tier
Tier identifier (free, creator, studio, enterprise).
- __init__(supports_replay, supports_cursor, supports_consumer_groups, max_retention_hours, max_buffer_size, tier)
- kumiho.resolve(kref)[source]
Resolve a kref URI to a file location.
This is a convenience function to get the file path for an artifact or the default artifact of a revision.
- Parameters:
kref (
str) – The kref URI to resolve.- Returns:
The file location string, or None if not resolvable.
- Return type:
Optional[str]
Example
>>> # Resolve a specific artifact >>> path = kumiho.resolve( ... "kref://film-project/chars/hero.model?r=3&a=mesh" ... ) >>> print(path) /projects/film/char/hero_v3.fbx
>>> # Resolve revision's default artifact >>> path = kumiho.resolve( ... "kref://film-project/chars/hero.model?r=3" ... )