Code Monkey home page Code Monkey logo

neo4j-graphql-py's Introduction

neo4j-graphql-py

A GraphQL to Cypher query execution layer for Neo4j and Python GraphQL implementations.

Installation and usage

Install

pip install neo4j-graphql-py

Then call neo4j_graphql() in your GraphQL resolver. Your GraphQL query will be translated to Cypher and the query passed to Neo4j.

from neo4j_graphql_py import neo4j_graphql

def resolve(obj, info, **kwargs):
    return neo4j_graphql(obj, info.context, info, **kwargs)

resolvers = {
  'Query': {
    'Movie':resolve
  }
}

What is neo4j-graphql-py

A package to make it easier to use GraphQL and Neo4j together. neo4j-graphql-py translates GraphQL queries to a single Cypher query, eliminating the need to write queries in GraphQL resolvers and for batching queries. It also exposes the Cypher query language through GraphQL via the @cypher schema directive.

How it works

Start with a GraphQL schema

GraphQL First Development is all about starting with a well defined GraphQL schema. Here we'll use the GraphQL schema IDL syntax:

type_defs = '''
directive @cypher(statement: String!) on FIELD_DEFINITION
directive @relation(name:String!, direction:String!) on FIELD_DEFINITION

type Movie {
  movieId: ID!
  title: String
  year: Int
  plot: String
  poster: String
  imdbRating: Float
  similar(first: Int = 3, offset: Int = 0): [Movie] @cypher(statement: "MATCH (this)-[:IN_GENRE]->(:Genre)<-[:IN_GENRE]-(o:Movie) RETURN o")
  degree: Int @cypher(statement: "RETURN SIZE((this)-->())")
  actors(first: Int = 3, offset: Int = 0): [Actor] @relation(name: "ACTED_IN", direction:"IN")
}

type Actor {
  id: ID!
  name: String
  movies: [Movie]
}


type Query {
  Movie(id: ID, title: String, year: Int, imdbRating: Float, first: Int, offset: Int): [Movie]
}
'''

We define two types, Movie and Actor as well as a top level Query Movie which becomes our entry point. This looks like a standard GraphQL schema, except for the use of two directives @relation and @cypher. In GraphQL directives allow us to annotate fields and provide an extension point for GraphQL.

  • @cypher directive - maps the specified Cypher query to the value of the field. In the Cypher query, this is bound to the current object being resolved.
  • @relation directive - used to indicate relationships in the data model. The name argument specifies the relationship type, and direction indicates the direction of the relationship ("IN" or "OUT" are valid values)

Translate GraphQL To Cypher

Inside each resolver, use neo4j-graphql() to generate the Cypher required to resolve the GraphQL query, passing through the query arguments, context and resolveInfo objects.

from neo4j_graphql_py import neo4j_graphql

resolvers = {
  # entry point to GraphQL service
  'Query': {
    'Movie': lambda obj, info, **kwargs: neo4j_graphql(obj, info.context,info, **kwargs)
  }
}

GraphQL to Cypher translation works by inspecting the GraphQL schema, the GraphQL query and arguments. For example, this simple GraphQL query

{
  Movie(title: "River Runs Through It, A") {
    title
    year
    imdbRating
  }
}

is translated into the Cypher query

MATCH (movie:Movie {title: "River Runs Through It, A"})
RETURN movie { .title , .year , .imdbRating } AS movie
SKIP 0

A slightly more complicated traversal

{
  Movie(title: "River Runs Through It, A") {
    title
    year
    imdbRating
    actors {
      name
    }
  }
}

becomes

MATCH (movie:Movie {title: "River Runs Through It, A"})
RETURN movie { .title , .year , .imdbRating, actors: [(movie)<-[:ACTED_IN]-(movie_actors:Actor) | movie_actors { .name }] }
AS movie
SKIP 0

@cypher directive

NOTE: The @cypher directive has a dependency on the APOC procedure library, specifically the function apoc.cypher.runFirstColumn to run sub-queries. If you'd like to make use of the @cypher feature you'll need to install appropriate version of APOC in Neo4j

GraphQL is fairly limited when it comes to expressing complex queries such as filtering, or aggregations. We expose the graph querying language Cypher through GraphQL via the @cypher directive. Annotate a field in your schema with the @cypher directive to map the results of that query to the annotated GraphQL field. For example:

type Movie {
  movieId: ID!
  title: String
  year: Int
  plot: String
  similar(first: Int = 3, offset: Int = 0): [Movie] @cypher(statement: "MATCH (this)-[:IN_GENRE]->(:Genre)<-[:IN_GENRE]-(o:Movie) RETURN o ORDER BY COUNT(*) DESC")
}

The field similar will be resolved using the Cypher query

MATCH (this)-[:IN_GENRE]->(:Genre)<-[:IN_GENRE]-(o:Movie) RETURN o ORDER BY COUNT(*) DESC

to find movies with overlapping Genres.

Querying a GraphQL field marked with a @cypher directive executes that query as a subquery:

GraphQL:

{
  Movie(title: "River Runs Through It, A") {
    title
    year
    imdbRating
    actors {
      name
    }
    similar(first: 3) {
      title
    }
  }
}

Cypher:

MATCH (movie:Movie {title: "River Runs Through It, A"})
RETURN movie { .title , .year , .imdbRating,
  actors: [(movie)<-[:ACTED_IN]-(movie_actors:Actor) | movie_actors { .name }],
  similar: [ x IN apoc.cypher.runFirstColumn("
        WITH {this} AS this
        MATCH (this)-[:IN_GENRE]->(:Genre)<-[:IN_GENRE]-(o:Movie)
        RETURN o",
        {this: movie}, true) | x { .title }][..3]
} AS movie
SKIP 0

Query Neo4j

Inject a Neo4j driver instance in the context of each GraphQL request and neo4j-graphql-py will query the Neo4j database and return the results to resolve the GraphQL query.

from neo4j_graphql_py import make_executable_schema
schema = make_executable_schema(type_defs, resolvers)
import neo4j
def context(request):
    global driver
    if driver is None:
        driver = neo4j.GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "neo4j"))

    return {'driver': driver, 'request': request}
from ariadne.asgi import GraphQL
import uvicorn
rootValue = {}
app = GraphQL(schema=schema, root_value=rootValue, context_value=context, debug=True)
uvicorn.run(app)

See /examples for complete examples using different GraphQL server libraries.

Benefits

  • Send a single query to the database
  • No need to write queries for each resolver
  • Exposes the power of the Cypher query language through GraphQL

Examples

See /examples for complete examples using different GraphQL server libraries.

neo4j-graphql-py's People

Contributors

usama0121 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

neo4j-graphql-py's Issues

Using neo4j-graphql-py for resolving part of a query

In my project not all types from the graphQL schema correspond to a label in neo4j.
That means that I have resolvers that are supposed to resolve only parts of the query using neo4j-graphql-py. Are there common usage patterns for this scenario? I'm particularly interested in retrieving the correct subgraph from the 'main' query.

In the following example, Customer and Location are GraphQL types and labels in neo4j, while GeoFeature is not.

{ GeoFeature (customerId: '200020') {
customer {
name
location {
lat
lon
}
id
}
}}

The resolver for the type GeoFeature is a custom function, while customer should be resolved with neo4j-graphql-py so that I don't need to touch the code when additional fields of the customer node are required in the query.
Any ideas? Thank you.

Using mutation is throwing me key error?

### CODE :

`import logging
import neo4j
import azure.functions as func
from neo4j_graphql_py import neo4j_graphql
from ariadne.wsgi import GraphQL
from ariadne import QueryType, make_executable_schema, MutationType

type_defs = '''
directive @cypher(statement: String!) on FIELD_DEFINITION
directive @relation(name:String!, direction:String!) on FIELD_DEFINITION

type RiskDomain {
name: String
colour: String
}

type Post {
id: String!
externalId: String
title: String
url: String
source: String
description : String
body: String
keywords: String
language: String
isSafe: Boolean
datePublished: String
imageUrl: String
imageHeight: Int
imageWidth: Int
active: Boolean
riskDomain(username: String): RiskDomain @cypher(statement:"""
MATCH (p:Post {id: this.id})
MATCH (rd:RiskDomain)<-[:FOLLOWS_RD]-(u:User)
WHERE u.name = $username
AND (rd)-[:CREATES]->(p)
RETURN rd
""")
}

type Query {
ListPost(user_name: String, first: Int, offset: Int): [Post] @cypher(
statement: """
CALL {
MATCH (p:Post)<-[:CREATES]-(rd:RiskDomain)<-[:FOLLOWS_RD]-(u:User)
WHERE u.name = $user_name
WITH p, rd
ORDER BY p.datePublished DESC
RETURN p
UNION
MATCH (p:Post)<-[:CREATES_POST]-(u1:User)
WHERE u1.name = $user_name
RETURN p
UNION
MATCH (p:Post)<-[:CREATES_POST]-(u2:User)<-[:FOLLOWS]-(u3:User)
WHERE u3.name = $user_name
RETURN p
}
RETURN p
ORDER BY p.datePublished DESC
"""
)

ListCVE_Posts(user_name: String, index_name: String): [Post] @cypher(
statement: "MATCH (a:Asset)<-[:HOLDS_AS]-(p:User) WHERE p.name = $user_name WITH apoc.text.replace(a.name, ' ', ' AND ') AS name WITH apoc.text.replace(name, name, '(' + name + ')' ) AS name WITH apoc.text.join(collect(name),' OR ') AS output CALL db.index.fulltext.queryNodes($index_name, output) YIELD node AS feed, score WITH feed MATCH (feed)<-[:CREATES]-(rd:CVEFeed)<-[:FOLLOWS_RD]-(u:User) WHERE u.name = $user_name RETURN feed")
}

type Mutation {
LikePost(user_refid: Int, post_id: Int): Post @cypher(
statement:"""
MATCH (u:User)
WHERE ID(u) = $user_refid
MATCH (p:Post)
WHERE ID(p) = $post_id
MERGE (u)-[r:LIKES_POST { userId: $user_refid, postId: $post_id}]->(p) ON CREATE SET r.datetime = datetime()
RETURN p
"""
)
FindPost(post_id: Int): [Post] @cypher(
statement:"""
MATCH(p:Post)
RETURN p
"""
)
}
'''

query = QueryType()
mutation = MutationType()

@query.field('ListCVE_Posts')
@query.field('ListPost')
@mutation.field('LikePost')
@mutation.field('FindPost')
def resolve(obj, info, **kwargs):
print(obj)
print(info)
return neo4j_graphql(obj, info.context, info, True, **kwargs)

schema = make_executable_schema(type_defs, [query, mutation])

driver = None

def context(request):
global driver
if driver is None:
driver = neo4j.GraphDatabase.driver("bolt://127.0.0.1:7687", auth=("neo4j", "test"))

return {'driver': driver, 'request': request}

rootValue = {}
app = GraphQL(schema=schema, root_value=rootValue, context_value=context, debug=True)

from azf_wsgi import AzureFunctionsWsgi

def main(req: func.HttpRequest) -> func.HttpResponse:
print("ok")
return AzureFunctionsWsgi(app).main(req)
`

### ERROR

GraphQL request:2:3
1 | mutation{
2 | FindPost(post_id:22) {
| ^
3 | title
Traceback (most recent call last):
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\graphql\execution\execute.py", line 623, in resolve_field_value_or_error
result = resolve_fn(source, info, **args)
File "C:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL\RiskPulseService_init_.py", line 99, in resolve
return neo4j_graphql(obj, info.context, info, True, **kwargs)
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\neo4j_graphql_py\main.py", line 23, in neo4j_graphql
query = cypher_query(context, resolve_info, **kwargs)
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\neo4j_graphql_py\main.py", line 55, in cypher_query
cyp_dir = cypher_directive(resolve_info.schema.query_type, resolve_info.field_name)
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\neo4j_graphql_py\utils.py", line 117, in fun
directive = field_directive(schema_type, field_name, directive_name)
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\neo4j_graphql_py\utils.py", line 112, in field_directive
return find(schema_type.fields[field_name].ast_node.directives, lambda d: d.name.value == directive_name)
KeyError: 'FindPost'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\graphql\execution\execute.py", line 668, in complete_value_catching_error
return_type, field_nodes, info, path, result
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\graphql\execution\execute.py", line 733, in complete_value
raise result
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\graphql\execution\execute.py", line 623, in resolve_field_value_or_error
result = resolve_fn(source, info, **args)
File "C:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL\RiskPulseService_init_.py", line 99, in resolve
return neo4j_graphql(obj, info.context, info, True, **kwargs)
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\neo4j_graphql_py\main.py", line 23, in neo4j_graphql
query = cypher_query(context, resolve_info, **kwargs)
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\neo4j_graphql_py\main.py", line 55, in cypher_query
cyp_dir = cypher_directive(resolve_info.schema.query_type, resolve_info.field_name)
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\neo4j_graphql_py\utils.py", line 117, in fun
directive = field_directive(schema_type, field_name, directive_name)
File "c:\Users\Shyam\Hailey\RiskPulse\Risk_Pulse_GraphQL.venv\lib\site-packages\neo4j_graphql_py\utils.py", line 112, in field_directive
return find(schema_type.fields[field_name].ast_node.directives, lambda d: d.name.value == directive_name)
graphql.error.graphql_error.GraphQLError: 'FindPost'

GraphQL request:2:3
1 | mutation{
2 | FindPost(post_id:22) {
| ^
3 | title

Looking for Authentication while implenting it.

I find this library an easy way to implement neo4j with graphql and django, but I didn't find any documentation or example for the Authorization, should I use ariadne-jwt or it has inbuilt support.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.