Adds a complete GraphQL API for querying MarkiTect database content including: CORE FEATURES: - Type-safe GraphQL schema with comprehensive field definitions - Full database access: markdown files, schemas, ASTs, and metadata - Advanced search capabilities with relevance scoring - Pagination support for efficient data access - Real-time schema introspection and development tools IMPLEMENTATION: - GraphQL schema definition with 6 core types (MarkdownFile, Schema, AST, etc.) - Complete resolver implementation with database integration - Flask-based GraphQL server with CORS support - GraphQL Playground for interactive development - Health check and schema introspection endpoints CLI INTEGRATION: - graphql-serve: Start GraphQL server with customizable options - graphql-query: Execute queries from command line (local/remote) - graphql-schema: Retrieve schema definition in SDL/JSON format - graphql-examples: Comprehensive usage examples and documentation API FEATURES: - Single item queries (by ID or filename) - List queries with filtering and pagination - Full-text search across files and schemas - Database statistics and analytics - AST querying with JSONPath expressions - Computed fields (word count, line count, etc.) TESTING: - Comprehensive test suite with 38 passing tests - Unit tests for schema, resolvers, server, and client - Integration tests for query execution - Error handling and edge case coverage - Mock and fixture support for isolated testing DOCUMENTATION: - Complete API documentation with examples - Usage guide for all CLI commands - Programming examples in Python and JavaScript - Performance optimization guidelines - Troubleshooting and security considerations The GraphQL interface enables developers to build rich applications on top of MarkiTect data with flexible, efficient querying capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
255 lines
8.0 KiB
Python
255 lines
8.0 KiB
Python
"""
|
|
GraphQL server implementation for MarkiTect.
|
|
|
|
Provides a standalone GraphQL server and integration components
|
|
for serving the MarkiTect GraphQL API.
|
|
"""
|
|
|
|
import json
|
|
from typing import Optional, Dict, Any
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from flask import Flask, request, jsonify
|
|
from flask_cors import CORS
|
|
FLASK_AVAILABLE = True
|
|
except ImportError:
|
|
FLASK_AVAILABLE = False
|
|
|
|
from .schema import schema
|
|
from .resolvers import Query
|
|
|
|
|
|
class GraphQLServer:
|
|
"""GraphQL server for MarkiTect API."""
|
|
|
|
def __init__(self, db_path: Optional[str] = None, enable_cors: bool = True):
|
|
"""
|
|
Initialize GraphQL server.
|
|
|
|
Args:
|
|
db_path: Path to MarkiTect database
|
|
enable_cors: Enable CORS for web browser access
|
|
"""
|
|
self.db_path = db_path or self._get_default_db_path()
|
|
self.enable_cors = enable_cors
|
|
self.app = None
|
|
|
|
if not FLASK_AVAILABLE:
|
|
raise ImportError(
|
|
"Flask is required for GraphQL server. Install with: pip install flask flask-cors"
|
|
)
|
|
|
|
def _get_default_db_path(self) -> str:
|
|
"""Get default database path."""
|
|
from .resolvers import get_default_database_path
|
|
return get_default_database_path()
|
|
|
|
def create_app(self) -> Flask:
|
|
"""Create Flask application with GraphQL endpoint."""
|
|
app = Flask(__name__)
|
|
|
|
if self.enable_cors:
|
|
CORS(app)
|
|
|
|
@app.route('/graphql', methods=['POST'])
|
|
def graphql_endpoint():
|
|
"""Handle GraphQL requests."""
|
|
try:
|
|
# Parse request data
|
|
data = request.get_json()
|
|
if not data:
|
|
return jsonify({'error': 'No JSON data provided'}), 400
|
|
|
|
query = data.get('query')
|
|
variables = data.get('variables', {})
|
|
operation_name = data.get('operationName')
|
|
|
|
if not query:
|
|
return jsonify({'error': 'No query provided'}), 400
|
|
|
|
# Execute GraphQL query
|
|
result = schema.execute(
|
|
query,
|
|
variables=variables,
|
|
operation_name=operation_name,
|
|
context={'db_path': self.db_path}
|
|
)
|
|
|
|
# Format response
|
|
response_data = {'data': result.data}
|
|
if result.errors:
|
|
response_data['errors'] = [
|
|
{'message': str(error)} for error in result.errors
|
|
]
|
|
|
|
return jsonify(response_data)
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
'errors': [{'message': f'Server error: {str(e)}'}]
|
|
}), 500
|
|
|
|
@app.route('/graphql', methods=['GET'])
|
|
def graphql_playground():
|
|
"""Serve GraphQL playground for development."""
|
|
return '''
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>MarkiTect GraphQL Playground</title>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/graphql-playground-react/build/static/css/index.css" />
|
|
</head>
|
|
<body>
|
|
<div id="root">
|
|
<style>
|
|
body { margin: 0; font-family: Open Sans, sans-serif; overflow: hidden; }
|
|
#root { height: 100vh; }
|
|
</style>
|
|
</div>
|
|
<script src="https://cdn.jsdelivr.net/npm/graphql-playground-react/build/static/js/middleware.js"></script>
|
|
<script>
|
|
window.addEventListener('load', function (event) {
|
|
GraphQLPlayground.init(document.getElementById('root'), {
|
|
endpoint: '/graphql',
|
|
settings: {
|
|
'general.betaUpdates': false,
|
|
'editor.theme': 'dark',
|
|
'editor.reuseHeaders': true,
|
|
'tracing.hideTracingResponse': true,
|
|
}
|
|
})
|
|
})
|
|
</script>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
@app.route('/schema', methods=['GET'])
|
|
def get_schema():
|
|
"""Get GraphQL schema definition."""
|
|
try:
|
|
from graphql.utilities import print_schema
|
|
schema_sdl = print_schema(schema.graphql_schema)
|
|
except (AttributeError, ImportError):
|
|
# Fallback to simple introspection
|
|
schema_sdl = str(schema)
|
|
|
|
return jsonify({
|
|
'schema': schema_sdl
|
|
})
|
|
|
|
@app.route('/health', methods=['GET'])
|
|
def health_check():
|
|
"""Health check endpoint."""
|
|
try:
|
|
# Test database connection
|
|
import sqlite3
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT 1")
|
|
conn.close()
|
|
|
|
return jsonify({
|
|
'status': 'healthy',
|
|
'database': 'connected',
|
|
'database_path': self.db_path
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'status': 'unhealthy',
|
|
'database': 'error',
|
|
'error': str(e)
|
|
}), 500
|
|
|
|
self.app = app
|
|
return app
|
|
|
|
def run(self, host: str = '127.0.0.1', port: int = 5000, debug: bool = False):
|
|
"""
|
|
Run the GraphQL server.
|
|
|
|
Args:
|
|
host: Host to bind to
|
|
port: Port to bind to
|
|
debug: Enable debug mode
|
|
"""
|
|
if not self.app:
|
|
self.create_app()
|
|
|
|
print(f"🚀 MarkiTect GraphQL Server starting...")
|
|
print(f"🔗 GraphQL endpoint: http://{host}:{port}/graphql")
|
|
print(f"🎮 GraphQL playground: http://{host}:{port}/graphql")
|
|
print(f"📊 Schema introspection: http://{host}:{port}/schema")
|
|
print(f"❤️ Health check: http://{host}:{port}/health")
|
|
|
|
self.app.run(host=host, port=port, debug=debug)
|
|
|
|
|
|
class GraphQLClient:
|
|
"""Simple GraphQL client for testing and CLI integration."""
|
|
|
|
def __init__(self, endpoint: str = "http://localhost:5000/graphql"):
|
|
"""
|
|
Initialize GraphQL client.
|
|
|
|
Args:
|
|
endpoint: GraphQL endpoint URL
|
|
"""
|
|
self.endpoint = endpoint
|
|
|
|
def execute(self, query: str, variables: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
"""
|
|
Execute GraphQL query.
|
|
|
|
Args:
|
|
query: GraphQL query string
|
|
variables: Query variables
|
|
|
|
Returns:
|
|
Query result dictionary
|
|
"""
|
|
try:
|
|
import requests
|
|
|
|
payload = {
|
|
'query': query,
|
|
'variables': variables or {}
|
|
}
|
|
|
|
response = requests.post(
|
|
self.endpoint,
|
|
json=payload,
|
|
headers={'Content-Type': 'application/json'}
|
|
)
|
|
|
|
return response.json()
|
|
|
|
except ImportError:
|
|
raise ImportError("requests is required for GraphQL client. Install with: pip install requests")
|
|
|
|
def execute_local(self, query: str, variables: Optional[Dict[str, Any]] = None, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
"""
|
|
Execute GraphQL query directly (without HTTP).
|
|
|
|
Args:
|
|
query: GraphQL query string
|
|
variables: Query variables
|
|
context: GraphQL context
|
|
|
|
Returns:
|
|
Query result dictionary
|
|
"""
|
|
result = schema.execute(
|
|
query,
|
|
variables=variables or {},
|
|
context=context or {}
|
|
)
|
|
|
|
response_data = {'data': result.data}
|
|
if result.errors:
|
|
response_data['errors'] = [
|
|
{'message': str(error)} for error in result.errors
|
|
]
|
|
|
|
return response_data |