Implements API layer for Information Spaces: - GraphQL schema types for spaces, documents, variables - GraphQL queries and mutations for space operations - CLI command group with all space management commands - Resolver functions connecting GraphQL to SpaceService - 38 unit tests for API components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
398 lines
14 KiB
Python
398 lines
14 KiB
Python
"""
|
|
GraphQL schema extension for Information Spaces.
|
|
|
|
Defines GraphQL types, queries, and mutations for the space system.
|
|
"""
|
|
|
|
import graphene
|
|
from graphene import ObjectType, String, Int, DateTime, List, Field, JSONString, Enum
|
|
from typing import Optional, Dict, Any
|
|
from datetime import datetime
|
|
|
|
|
|
# Enums
|
|
class SpaceStatusEnum(Enum):
|
|
"""GraphQL enum for space status."""
|
|
DRAFT = "draft"
|
|
ACTIVE = "active"
|
|
ARCHIVED = "archived"
|
|
DELETED = "deleted"
|
|
|
|
|
|
class SyncDirectionEnum(Enum):
|
|
"""GraphQL enum for sync direction."""
|
|
SPACE_TO_DIRECTORY = "space_to_directory"
|
|
DIRECTORY_TO_SPACE = "directory_to_space"
|
|
BIDIRECTIONAL = "bidirectional"
|
|
|
|
|
|
class ConflictResolutionEnum(Enum):
|
|
"""GraphQL enum for conflict resolution strategy."""
|
|
SPACE_WINS = "space_wins"
|
|
DIRECTORY_WINS = "directory_wins"
|
|
NEWER_WINS = "newer_wins"
|
|
SKIP = "skip"
|
|
|
|
|
|
# Types
|
|
class SpaceConfigType(ObjectType):
|
|
"""GraphQL type for space configuration."""
|
|
default_variant = String(description="Default export variant")
|
|
enable_caching = graphene.Boolean(description="Whether caching is enabled")
|
|
theme = String(description="Theme for rendering")
|
|
history_enabled = graphene.Boolean(description="Whether history tracking is enabled")
|
|
variable_scope = String(description="Default variable scope")
|
|
|
|
|
|
class SpaceMetadataType(ObjectType):
|
|
"""GraphQL type for space metadata."""
|
|
tags = List(String, description="Tags for categorization")
|
|
author = String(description="Author identifier")
|
|
custom = JSONString(description="Custom metadata fields")
|
|
|
|
|
|
class InformationSpaceType(ObjectType):
|
|
"""GraphQL type for Information Spaces."""
|
|
id = String(required=True, description="Unique space identifier")
|
|
name = String(required=True, description="Space name")
|
|
description = String(description="Space description")
|
|
status = String(description="Space lifecycle status")
|
|
config = Field(SpaceConfigType, description="Space configuration")
|
|
metadata = Field(SpaceMetadataType, description="Space metadata")
|
|
parent_space_id = String(description="Parent space ID for inheritance")
|
|
created_at = DateTime(description="Creation timestamp")
|
|
updated_at = DateTime(description="Last update timestamp")
|
|
|
|
# Computed fields
|
|
document_count = Int(description="Number of documents in space")
|
|
variable_count = Int(description="Number of variables defined")
|
|
|
|
|
|
class SpaceDocumentType(ObjectType):
|
|
"""GraphQL type for documents in a space."""
|
|
id = String(required=True, description="Document association ID")
|
|
space_id = String(required=True, description="Parent space ID")
|
|
document_id = String(required=True, description="Document identifier")
|
|
space_path = String(required=True, description="Path within space")
|
|
order_index = Int(description="Order index for sorting")
|
|
metadata = JSONString(description="Document-specific metadata")
|
|
added_at = DateTime(description="When document was added to space")
|
|
|
|
|
|
class SpaceVariableType(ObjectType):
|
|
"""GraphQL type for space variables."""
|
|
space_id = String(required=True, description="Parent space ID")
|
|
name = String(required=True, description="Variable name")
|
|
value = JSONString(description="Variable value")
|
|
scope = String(description="Variable scope (space, document, request)")
|
|
|
|
|
|
class RenderResultType(ObjectType):
|
|
"""GraphQL type for render results."""
|
|
content = String(required=True, description="Rendered content")
|
|
format = String(required=True, description="Output format")
|
|
content_hash = String(description="Hash of rendered content")
|
|
source_hash = String(description="Hash of source content")
|
|
document_id = String(description="Rendered document ID")
|
|
cached = graphene.Boolean(description="Whether result was from cache")
|
|
|
|
|
|
class SyncResultType(ObjectType):
|
|
"""GraphQL type for sync results."""
|
|
success = graphene.Boolean(required=True, description="Whether sync succeeded")
|
|
created_count = Int(description="Number of items created")
|
|
updated_count = Int(description="Number of items updated")
|
|
deleted_count = Int(description="Number of items deleted")
|
|
conflict_count = Int(description="Number of conflicts encountered")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
class SyncConflictType(ObjectType):
|
|
"""GraphQL type for sync conflicts."""
|
|
path = String(required=True, description="Conflicting path")
|
|
space_hash = String(description="Content hash in space")
|
|
directory_hash = String(description="Content hash in directory")
|
|
resolution = String(description="How conflict was resolved")
|
|
winner = String(description="Which side won")
|
|
|
|
|
|
# Input Types
|
|
class SpaceConfigInput(graphene.InputObjectType):
|
|
"""Input type for space configuration."""
|
|
default_variant = String()
|
|
enable_caching = graphene.Boolean()
|
|
theme = String()
|
|
history_enabled = graphene.Boolean()
|
|
variable_scope = String()
|
|
|
|
|
|
class SpaceMetadataInput(graphene.InputObjectType):
|
|
"""Input type for space metadata."""
|
|
tags = List(String)
|
|
author = String()
|
|
custom = JSONString()
|
|
|
|
|
|
class CreateSpaceInput(graphene.InputObjectType):
|
|
"""Input for creating a space."""
|
|
name = String(required=True)
|
|
description = String()
|
|
config = graphene.Argument(SpaceConfigInput)
|
|
metadata = graphene.Argument(SpaceMetadataInput)
|
|
parent_space_id = String()
|
|
|
|
|
|
class UpdateSpaceInput(graphene.InputObjectType):
|
|
"""Input for updating a space."""
|
|
name = String()
|
|
description = String()
|
|
config = graphene.Argument(SpaceConfigInput)
|
|
metadata = graphene.Argument(SpaceMetadataInput)
|
|
|
|
|
|
class AddDocumentInput(graphene.InputObjectType):
|
|
"""Input for adding a document to a space."""
|
|
document_id = String(required=True)
|
|
space_path = String(required=True)
|
|
order_index = Int()
|
|
metadata = JSONString()
|
|
|
|
|
|
class RenderOptionsInput(graphene.InputObjectType):
|
|
"""Input for render options."""
|
|
theme = String()
|
|
include_toc = graphene.Boolean()
|
|
force_refresh = graphene.Boolean()
|
|
|
|
|
|
class SyncOptionsInput(graphene.InputObjectType):
|
|
"""Input for sync options."""
|
|
direction = String()
|
|
conflict_resolution = String()
|
|
dry_run = graphene.Boolean()
|
|
|
|
|
|
# Payload Types
|
|
class CreateSpacePayload(ObjectType):
|
|
"""Payload for createSpace mutation."""
|
|
space = Field(InformationSpaceType, description="The created space")
|
|
success = graphene.Boolean(description="Whether operation succeeded")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
class UpdateSpacePayload(ObjectType):
|
|
"""Payload for updateSpace mutation."""
|
|
space = Field(InformationSpaceType, description="The updated space")
|
|
success = graphene.Boolean(description="Whether operation succeeded")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
class DeleteSpacePayload(ObjectType):
|
|
"""Payload for deleteSpace mutation."""
|
|
success = graphene.Boolean(description="Whether operation succeeded")
|
|
deleted_id = String(description="ID of deleted space")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
class AddDocumentPayload(ObjectType):
|
|
"""Payload for addDocument mutation."""
|
|
document = Field(SpaceDocumentType, description="The added document")
|
|
success = graphene.Boolean(description="Whether operation succeeded")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
class RemoveDocumentPayload(ObjectType):
|
|
"""Payload for removeDocument mutation."""
|
|
success = graphene.Boolean(description="Whether operation succeeded")
|
|
removed_path = String(description="Path of removed document")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
class SetVariablePayload(ObjectType):
|
|
"""Payload for setVariable mutation."""
|
|
variable = Field(SpaceVariableType, description="The set variable")
|
|
success = graphene.Boolean(description="Whether operation succeeded")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
class RenderDocumentPayload(ObjectType):
|
|
"""Payload for renderDocument mutation."""
|
|
result = Field(RenderResultType, description="Render result")
|
|
success = graphene.Boolean(description="Whether operation succeeded")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
class SyncSpacePayload(ObjectType):
|
|
"""Payload for syncSpace mutation."""
|
|
result = Field(SyncResultType, description="Sync result")
|
|
conflicts = List(SyncConflictType, description="Conflicts encountered")
|
|
success = graphene.Boolean(description="Whether operation succeeded")
|
|
errors = List(String, description="Error messages")
|
|
|
|
|
|
# Query Extension
|
|
class SpaceQuery(ObjectType):
|
|
"""Query extension for spaces."""
|
|
|
|
# Single space queries
|
|
space = Field(
|
|
InformationSpaceType,
|
|
id=String(description="Space ID"),
|
|
name=String(description="Space name"),
|
|
description="Get a specific space"
|
|
)
|
|
|
|
# List queries
|
|
spaces = List(
|
|
InformationSpaceType,
|
|
status=String(description="Filter by status"),
|
|
limit=Int(default_value=50, description="Maximum results"),
|
|
offset=Int(default_value=0, description="Pagination offset"),
|
|
description="List all spaces"
|
|
)
|
|
|
|
# Document queries
|
|
space_documents = List(
|
|
SpaceDocumentType,
|
|
space_id=String(required=True, description="Space ID"),
|
|
description="List documents in a space"
|
|
)
|
|
|
|
space_document = Field(
|
|
SpaceDocumentType,
|
|
space_id=String(required=True, description="Space ID"),
|
|
space_path=String(required=True, description="Document path"),
|
|
description="Get a specific document in a space"
|
|
)
|
|
|
|
# Variable queries
|
|
space_variables = List(
|
|
SpaceVariableType,
|
|
space_id=String(required=True, description="Space ID"),
|
|
scope=String(description="Filter by scope"),
|
|
description="List variables in a space"
|
|
)
|
|
|
|
space_variable = Field(
|
|
SpaceVariableType,
|
|
space_id=String(required=True, description="Space ID"),
|
|
name=String(required=True, description="Variable name"),
|
|
description="Get a specific variable"
|
|
)
|
|
|
|
|
|
# Mutation Extension
|
|
class SpaceMutation(ObjectType):
|
|
"""Mutation extension for spaces."""
|
|
|
|
# Space lifecycle
|
|
create_space = Field(
|
|
CreateSpacePayload,
|
|
input=graphene.Argument(CreateSpaceInput, required=True),
|
|
description="Create a new space"
|
|
)
|
|
|
|
update_space = Field(
|
|
UpdateSpacePayload,
|
|
id=String(required=True, description="Space ID"),
|
|
input=graphene.Argument(UpdateSpaceInput, required=True),
|
|
description="Update a space"
|
|
)
|
|
|
|
delete_space = Field(
|
|
DeleteSpacePayload,
|
|
id=String(required=True, description="Space ID"),
|
|
description="Delete a space"
|
|
)
|
|
|
|
activate_space = Field(
|
|
UpdateSpacePayload,
|
|
id=String(required=True, description="Space ID"),
|
|
description="Activate a draft space"
|
|
)
|
|
|
|
archive_space = Field(
|
|
UpdateSpacePayload,
|
|
id=String(required=True, description="Space ID"),
|
|
description="Archive a space"
|
|
)
|
|
|
|
# Document operations
|
|
add_document = Field(
|
|
AddDocumentPayload,
|
|
space_id=String(required=True, description="Space ID"),
|
|
input=graphene.Argument(AddDocumentInput, required=True),
|
|
description="Add a document to a space"
|
|
)
|
|
|
|
remove_document = Field(
|
|
RemoveDocumentPayload,
|
|
space_id=String(required=True, description="Space ID"),
|
|
space_path=String(required=True, description="Document path"),
|
|
description="Remove a document from a space"
|
|
)
|
|
|
|
# Variable operations
|
|
set_variable = Field(
|
|
SetVariablePayload,
|
|
space_id=String(required=True, description="Space ID"),
|
|
name=String(required=True, description="Variable name"),
|
|
value=JSONString(required=True, description="Variable value"),
|
|
scope=String(description="Variable scope"),
|
|
description="Set a variable in a space"
|
|
)
|
|
|
|
delete_variable = Field(
|
|
SetVariablePayload,
|
|
space_id=String(required=True, description="Space ID"),
|
|
name=String(required=True, description="Variable name"),
|
|
description="Delete a variable from a space"
|
|
)
|
|
|
|
# Rendering
|
|
render_document = Field(
|
|
RenderDocumentPayload,
|
|
space_id=String(required=True, description="Space ID"),
|
|
document_path=String(required=True, description="Document path"),
|
|
options=graphene.Argument(RenderOptionsInput),
|
|
description="Render a document to HTML"
|
|
)
|
|
|
|
render_space = Field(
|
|
RenderDocumentPayload,
|
|
space_id=String(required=True, description="Space ID"),
|
|
options=graphene.Argument(RenderOptionsInput),
|
|
description="Render all documents in a space"
|
|
)
|
|
|
|
# Sync operations
|
|
sync_space = Field(
|
|
SyncSpacePayload,
|
|
space_id=String(required=True, description="Space ID"),
|
|
directory=String(required=True, description="Directory path"),
|
|
options=graphene.Argument(SyncOptionsInput),
|
|
description="Sync space with directory"
|
|
)
|
|
|
|
export_space = Field(
|
|
SyncSpacePayload,
|
|
space_id=String(required=True, description="Space ID"),
|
|
directory=String(required=True, description="Export directory"),
|
|
description="Export space to directory"
|
|
)
|
|
|
|
import_directory = Field(
|
|
SyncSpacePayload,
|
|
space_id=String(required=True, description="Target space ID"),
|
|
directory=String(required=True, description="Directory to import"),
|
|
description="Import directory into space"
|
|
)
|
|
|
|
# Cache operations
|
|
invalidate_cache = Field(
|
|
graphene.Boolean,
|
|
space_id=String(required=True, description="Space ID"),
|
|
document_path=String(description="Optional specific document"),
|
|
description="Invalidate render cache"
|
|
)
|