Skip to content

GraphQL API

ninja_gql

ninja-gql — GraphQL layer generator for Ninja Stack.

__all__ module-attribute

__all__ = ['GqlGenerator', 'build_schema']

GqlGenerator

GqlGenerator(schema: AgenticSchema)

Generates Strawberry types, input types, queries, and mutations from an ASD.

Source code in libs/ninja-gql/src/ninja_gql/generator.py
def __init__(self, schema: AgenticSchema) -> None:
    self.schema = schema
    self._types: dict[str, type] = {}
    self._input_types: dict[str, type] = {}
    self._entity_map: dict[str, EntitySchema] = {e.name: e for e in schema.entities}
    self._rel_by_source: dict[str, list[RelationshipSchema]] = {}
    for rel in schema.relationships:
        self._rel_by_source.setdefault(rel.source_entity, []).append(rel)

schema instance-attribute

schema = schema

generate_types

generate_types() -> dict[str, type]

Create a Strawberry @strawberry.type class per entity.

Returns a mapping {EntityName: StrawberryType}.

Source code in libs/ninja-gql/src/ninja_gql/generator.py
def generate_types(self) -> dict[str, type]:
    """Create a Strawberry ``@strawberry.type`` class per entity.

    Returns a mapping ``{EntityName: StrawberryType}``.
    """
    if self._types:
        return self._types

    # First pass: create basic types (no relationships yet)
    for entity in self.schema.entities:
        self._types[entity.name] = self._make_type(entity)

    # Second pass: wire relationship fields
    for entity in self.schema.entities:
        self._attach_relationships(entity)

    return self._types

generate_input_types

generate_input_types() -> dict[str, tuple[type, type]]

Create Strawberry input types for create and update operations.

Returns {EntityName: (CreateInput, UpdateInput)}.

Source code in libs/ninja-gql/src/ninja_gql/generator.py
def generate_input_types(self) -> dict[str, tuple[type, type]]:
    """Create Strawberry input types for create and update operations.

    Returns ``{EntityName: (CreateInput, UpdateInput)}``.
    """
    if self._input_types:
        return self._input_types

    for entity in self.schema.entities:
        create_cls, update_cls = self._make_input_types(entity)
        self._input_types[entity.name] = (create_cls, update_cls)
    return self._input_types

get_type

get_type(entity_name: str) -> type

Return the generated Strawberry type for entity_name.

Source code in libs/ninja-gql/src/ninja_gql/generator.py
def get_type(self, entity_name: str) -> type:
    """Return the generated Strawberry type for *entity_name*."""
    if not self._types:
        self.generate_types()
    return self._types[entity_name]

has_embeddable_fields

has_embeddable_fields(entity: EntitySchema) -> bool

Return True if the entity has any field with embedding config.

Source code in libs/ninja-gql/src/ninja_gql/generator.py
def has_embeddable_fields(self, entity: EntitySchema) -> bool:
    """Return True if the entity has any field with embedding config."""
    return any(f.embedding is not None for f in entity.fields)

build_schema

build_schema(
    asd: AgenticSchema,
    repo_getter: Callable[[str], Repository[Any]]
    | None = None,
    agent_router: AgentRouter | None = None,
) -> Schema

Build a Strawberry Schema from an Agentic Schema Definition.

Parameters

asd: The project's ASD. repo_getter: A callable (entity_name) -> Repository. When None a stub that raises at call-time is used (useful for schema-only validation). agent_router: Optional agent router for ask_* queries.

Source code in libs/ninja-gql/src/ninja_gql/schema.py
def build_schema(
    asd: AgenticSchema,
    repo_getter: Callable[[str], Repository[Any]] | None = None,
    agent_router: AgentRouter | None = None,
) -> strawberry.Schema:
    """Build a Strawberry ``Schema`` from an Agentic Schema Definition.

    Parameters
    ----------
    asd:
        The project's ASD.
    repo_getter:
        A callable ``(entity_name) -> Repository``.  When ``None`` a stub
        that raises at call-time is used (useful for schema-only validation).
    agent_router:
        Optional agent router for ``ask_*`` queries.
    """
    gen = GqlGenerator(asd)
    types = gen.generate_types()
    gen.generate_input_types()

    if repo_getter is None:

        def _no_repo(name: str) -> Repository[Any]:
            raise RuntimeError(f"No repository configured for {name}")

        repo_getter = _no_repo

    # -- build Query fields --------------------------------------------------
    query_annotations: dict[str, Any] = {}
    query_ns: dict[str, Any] = {"__annotations__": query_annotations}

    for entity in asd.entities:
        gql_type = types[entity.name]
        snake = _snake(entity.name)

        # get
        get_name = f"get_{snake}"
        get_fn = make_get_resolver(entity, gql_type, repo_getter)
        query_ns[get_name] = strawberry.field(resolver=get_fn)

        # list
        list_name = f"list_{snake}"
        list_fn = make_list_resolver(entity, gql_type, repo_getter)
        query_ns[list_name] = strawberry.field(resolver=list_fn)

        # semantic search (only for entities with embeddable fields)
        if gen.has_embeddable_fields(entity):
            search_name = f"search_{snake}"
            search_fn = make_search_resolver(entity, gql_type, repo_getter)
            query_ns[search_name] = strawberry.field(resolver=search_fn)

    # agent queries per domain
    for domain in asd.domains:
        ask_name = f"ask_{domain.name.lower().replace(' ', '_')}"
        ask_fn = make_agent_query_resolver(domain.name, agent_router)
        query_ns[ask_name] = strawberry.field(resolver=ask_fn)

    query_cls = type("Query", (), query_ns)
    Query = strawberry.type(query_cls, description="Auto-generated root Query")

    # -- build Mutation fields -----------------------------------------------
    mutation_annotations: dict[str, Any] = {}
    mutation_ns: dict[str, Any] = {"__annotations__": mutation_annotations}

    for entity in asd.entities:
        gql_type = types[entity.name]
        snake = _snake(entity.name)

        create_fn = make_create_resolver(entity, gql_type, repo_getter)
        mutation_ns[f"create_{snake}"] = strawberry.mutation(resolver=create_fn)

        update_fn = make_update_resolver(entity, gql_type, repo_getter)
        mutation_ns[f"update_{snake}"] = strawberry.mutation(resolver=update_fn)

        delete_fn = make_delete_resolver(entity, repo_getter)
        mutation_ns[f"delete_{snake}"] = strawberry.mutation(resolver=delete_fn)

    mutation_cls = type("Mutation", (), mutation_ns)
    Mutation = strawberry.type(mutation_cls, description="Auto-generated root Mutation")

    return strawberry.Schema(query=Query, mutation=Mutation)