Skip to content
89 changes: 89 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build and Development Commands

```bash
# Setup virtual environment and install dependencies
make bootstrap

# Run all tests (pyflakes linting + pytest)
make test

# Run only pytest tests
make test-pytest

# Run only pyflakes linting
make test-pyflakes

# Run a single test file
pytest tests/test_core.py

# Run a specific test
pytest tests/test_core.py::test_resolver_is_dispatched_using_type_of_query

# Run tests across all Python versions with tox
make test-all
```

## Architecture Overview

GraphLayer is a high-performance GraphQL library that resolves queries per request shape rather than per response node. This avoids the N+1 problem without DataLoader-style batching.

### Core Concepts

**Graph and Resolvers** (`graphlayer/core.py`): The `Graph` dispatches queries to resolvers based on `query.type`. Resolvers are registered with `@g.resolver(type)` and receive `(graph, query)`. Use `graph.resolve(query)` to delegate to other resolvers.

**Schema Types** (`graphlayer/schema.py`): Defines GraphQL types - `ObjectType`, `InterfaceType`, `ListType`, `NullableType`, `EnumType`, `InputObjectType`, and scalars (`Int`, `String`, `Boolean`, `Float`). Object types have `fields` defined via `g.field(name, type, params)`.

**Queries**: Each type has a corresponding query class (`ObjectQuery`, `ListQuery`, `ScalarQuery`, etc.). Resolvers receive queries that describe what fields/data are requested, enabling optimized data fetching.

**Dependency Injection**: Use `@g.dependencies(name=Key)` to inject dependencies into resolvers. Dependencies are provided when creating the graph via `graph_definition.create_graph({Key: value})`.

### Module Structure

- `graphlayer/` - Core library
- `core.py` - Graph, resolvers, dependency injection
- `schema.py` - Type system and query classes
- `resolvers.py` - Helper functions (`root_object_resolver`, `create_object_builder`)
- `sqlalchemy.py` - SQLAlchemy integration (`sql_table_resolver`, `select`, `join`, `expression`)
- `connections.py` - Relay-style pagination
- `unions.py` - Union type resolution
- `graphql/` - GraphQL parsing and schema generation
- `tests/` - Test suite using pytest and precisely matchers
- `examples/` - Example applications including SQLAlchemy integration

### Key Patterns

**Root Resolver Pattern**:
```python
resolve_root = g.root_object_resolver(Root)

@resolve_root.field(Root.fields.books)
def resolve_books(graph, query, args):
return graph.resolve(gsql.select(query).where(...))
```

**SQLAlchemy Table Resolver**:
```python
resolve_books = gsql.sql_table_resolver(
Book, BookRecord,
fields={
Book.fields.title: gsql.expression(BookRecord.title),
Book.fields.author: lambda graph, field_query: gsql.join(
key=BookRecord.author_id,
resolve=lambda ids: graph.resolve(gsql.select(field_query.type_query).by(AuthorRecord.id, ids)),
),
},
)
```

**Custom Query Types**: Create custom query classes with a `type` attribute to enable filtering/keying:
```python
class BookQuery:
def __init__(self, object_query, genre=None):
self.type = BookQuery
self.object_query = object_query
self.genre = genre
```
10 changes: 7 additions & 3 deletions examples/intro_sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,18 @@ class BookRecord(Base):
g.field("author", type=Author),
))


book_resolver = gsql.sql_table_resolver(
Book,
BookRecord,
fields={
Book.fields.title: gsql.expression(BookRecord.title),
Book.fields.author: g.single(gsql.sql_join({
BookRecord.author_id: AuthorRecord.id,
})),
Book.fields.author: lambda graph, field_query: gsql.join(
key=BookRecord.author_id,
resolve=lambda author_ids: graph.resolve(
gsql.select(field_query.type_query).by(AuthorRecord.id, author_ids),
),
),
},
)

Expand Down
4 changes: 2 additions & 2 deletions examples/sqlalchemy/booksapp/graph/authors.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ def select_by_id(type_query, ids):
Author,
database.Author,
fields=lambda: {
Author.fields.books: gsql.join(
Author.fields.books: lambda graph, field_query,: gsql.join(
key=database.Author.id,
resolve=lambda graph, field_query, ids: graph.resolve(
resolve=lambda ids: graph.resolve(
books.BookQuery.select_by_author_ids(field_query.type_query, author_ids=ids),
),
),
Expand Down
4 changes: 2 additions & 2 deletions examples/sqlalchemy/booksapp/graph/books.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ def select_by_author_ids(type_query, author_ids):
Book,
database.Book,
fields=lambda: {
Book.fields.author: gsql.join(
Book.fields.author: lambda graph, field_query: gsql.join(
key=database.Book.author_id,
resolve=lambda graph, field_query, ids: graph.resolve(
resolve=lambda ids: graph.resolve(
authors.AuthorQuery.select_by_id(field_query.type_query, ids=ids),
),
),
Expand Down
2 changes: 1 addition & 1 deletion examples/sqlalchemy/booksapp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ def graphql():
finally:
session.close()

return flask.jsonify({"data": response})
return flask.jsonify({"data": response.data})
Loading