Skip to main content
Glama

Django MCP Server

routing.py9.93 kB
from __future__ import annotations import inspect import re from collections.abc import Iterable from enum import Enum from pathlib import Path from typing import Any from typing import Literal from django.urls import get_resolver from django.urls.resolvers import URLPattern from django.urls.resolvers import URLResolver from pydantic import BaseModel class ViewType(Enum): CLASS = "class" FUNCTION = "function" class ViewMethod(Enum): DELETE = "DELETE" GET = "GET" HEAD = "HEAD" PATCH = "PATCH" POST = "POST" PUT = "PUT" OPTIONS = "OPTIONS" TRACE = "TRACE" class FunctionViewSchema(BaseModel): """Schema for function-based view. Fields: name: Fully qualified view name (module.function) type: Always ViewType.FUNCTION source_path: Path to source file, or Path("unknown") methods: List of allowed HTTP methods. Empty list indicates methods could not be determined. Django's built-in method decorators (@require_GET, @require_POST, @require_http_methods) are automatically detected via closure inspection. """ name: str type: Literal[ViewType.FUNCTION] source_path: Path methods: list[ViewMethod] @classmethod def from_callback(cls, callback: Any): view_func = get_view_func(callback) name = get_view_name(view_func) source_path = get_source_file_path(view_func) methods = _extract_methods_from_closure(callback) if methods is None: methods = [] return FunctionViewSchema( name=name, type=ViewType.FUNCTION, source_path=source_path, methods=methods, ) class ClassViewSchema(BaseModel): """Schema for class-based view. Fields: name: Fully qualified view name (module.ClassName) type: Always ViewType.CLASS source_path: Path to source file, or Path("unknown") methods: List of HTTP methods actually implemented by the view. Determined by checking which method handlers (get, post, etc.) are defined on the class. class_bases: List of base class names, excluding 'object'. Example: ['ListView', 'LoginRequiredMixin'] """ name: str type: Literal[ViewType.CLASS] source_path: Path methods: list[ViewMethod] class_bases: list[str] @classmethod def from_callback(cls, callback: Any): view_func = get_view_func(callback) name = get_view_name(view_func) source_path = get_source_file_path(view_func) bases = [ base.__name__ for base in view_func.__bases__ if base.__name__ != "object" ] class_bases = bases if bases else [] implemented_methods = [ method for method in ViewMethod if hasattr(view_func, method.value.lower()) ] methods = implemented_methods if implemented_methods else [] return cls( name=name, type=ViewType.CLASS, source_path=source_path, class_bases=class_bases, methods=methods, ) ViewSchema = FunctionViewSchema | ClassViewSchema class RouteSchema(BaseModel): """Schema for a complete Django URL route. Fields: pattern: Full URL pattern string (e.g., "blog/<int:pk>/") name: Route name for reverse URL lookup, or None if unnamed namespace: URL namespace (e.g., "admin"), or None if not namespaced parameters: List of URL parameter names extracted from pattern view: View handler (FunctionViewSchema or ClassViewSchema) """ pattern: str name: str | None namespace: str | None parameters: list[str] view: ViewSchema def get_source_file_path(obj: Any) -> Path: """Get the source file path for a function or class. Returns Path("unknown") if the source cannot be determined. """ try: return Path(inspect.getfile(obj)) except (TypeError, OSError): return Path("unknown") def extract_url_parameters(pattern: str) -> list[str]: """Extract parameter names from a URL pattern. Supports Django's standard path converters (int, str, slug, uuid, path) and any custom converter names using word characters (a-z, A-Z, 0-9, _). Args: pattern: Django URL pattern string Returns: List of parameter names extracted from the pattern Examples: >>> extract_url_parameters("blog/<int:pk>/") ['pk'] >>> extract_url_parameters("blog/<pk>/") ['pk'] >>> extract_url_parameters("api/<uuid:id>/posts/<int:post_id>/") ['id', 'post_id'] """ # Matches <converter:name> or <name> and captures the parameter name param_regex = r"<(?:\w+:)?(\w+)>" return re.findall(param_regex, pattern) def _extract_methods_from_closure(view_func: Any) -> list[ViewMethod] | None: """Extract allowed HTTP methods from Django decorator closure. Django's method decorators (@require_GET, @require_http_methods, etc.) store the allowed methods in the function's closure. This extracts them. Returns: List of allowed ViewMethod enums if decorator found, None otherwise. """ if not hasattr(view_func, "__closure__") or not view_func.__closure__: return None for cell in view_func.__closure__: try: content = cell.cell_contents if isinstance(content, list) and content: if all(isinstance(m, str) for m in content): methods = [] for method_str in content: if method_str in ViewMethod.__members__: methods.append(ViewMethod[method_str]) if methods: return methods except ValueError: # pragma: no cover continue return None def get_view_func(callback: Any): """Extract the actual view function or class from a callback. Unwraps decorators and extracts view_class from .as_view() callbacks. Returns: The underlying function or class object """ view_func = callback if hasattr(view_func, "view_class"): view_func = view_func.view_class while hasattr(view_func, "__wrapped__"): view_func = view_func.__wrapped__ return view_func def get_view_name(view_func: Any): """Get the fully qualified name of a view function or class. Returns: Fully qualified name (module.name) """ module = inspect.getmodule(view_func) assert module is not None, f"Could not determine module for {view_func}" return f"{module.__name__}.{view_func.__name__}" def extract_routes( patterns: Iterable[URLPattern | URLResolver], prefix: str = "", namespace: str | None = None, ) -> list[RouteSchema]: """Recursively extract routes from URL patterns.""" routes = [] for pattern in patterns: if isinstance(pattern, URLResolver): current_namespace = pattern.namespace full_namespace: str | None if namespace and current_namespace: full_namespace = f"{namespace}:{current_namespace}" elif current_namespace: full_namespace = current_namespace else: full_namespace = None extracted_routes = extract_routes( pattern.url_patterns, prefix + str(pattern.pattern), full_namespace, ) routes.extend(extracted_routes) elif isinstance(pattern, URLPattern): full_pattern = prefix + str(pattern.pattern) parameters = extract_url_parameters(full_pattern) view_func = get_view_func(pattern.callback) if inspect.isclass(view_func): view_schema = ClassViewSchema.from_callback(pattern.callback) else: view_schema = FunctionViewSchema.from_callback(pattern.callback) route = RouteSchema( pattern=full_pattern, name=pattern.name, namespace=namespace, parameters=parameters, view=view_schema, ) routes.append(route) return routes def get_all_routes() -> list[RouteSchema]: """Get all Django URL routes by recursively walking URLconf. Traverses the entire URL resolver tree starting from ROOT_URLCONF, extracting route patterns, view metadata, namespaces, and parameters. Returns: List of RouteSchema objects, one per URL pattern Note: For projects with many routes (1000+), this may take a few seconds on first call. Results are not cached. """ resolver = get_resolver() routes = extract_routes(resolver.url_patterns) return routes def filter_routes( routes: list[RouteSchema], method: ViewMethod | None = None, name: str | None = None, pattern: str | None = None, ) -> list[RouteSchema]: """Filter routes using contains matching on each parameter. All filters are AND'd together - routes must match all provided filters. Args: method: ViewMethod enum or None for filtering by HTTP method name: Route name substring for filtering (case-sensitive) pattern: URL pattern substring for filtering (case-sensitive) Returns: Filtered list of routes matching all provided criteria Raises: ValueError: If method is not a valid HTTP method name """ if method: routes = [r for r in routes if not r.view.methods or method in r.view.methods] if name is not None: routes = [r for r in routes if r.name and name in r.name] if pattern is not None: routes = [r for r in routes if pattern in r.pattern] return routes

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/joshuadavidthomas/mcp-django'

If you have feedback or need assistance with the MCP directory API, please join our Discord server