Build a service with the Python SDK¶
Build a movie cast service with the Python SDK and FastAPI. A request to GET /movies/3/actors returns JSON for Shadows in Paradise and its actors.
Before you start¶
- Follow Prepare NebulaGraph Database Developer Edition to start NebulaGraph Database Developer Edition and load the
moviesample dataset. - Install Python 3.10 or later and know Python functions, dictionaries, and exception handling.
- Have a NebulaGraph Database Developer Edition username and password. The application computer must be able to reach the NebulaGraph Database Developer Edition query port.
The terminal commands below are for macOS and Linux. Run them on the computer running the application, which can be the development machine running Docker or another computer that can reach NebulaGraph Database Developer Edition.
1. Create the project and install dependencies¶
mkdir movie-sdk-app
cd movie-sdk-app
python3 -m venv .venv
source .venv/bin/activate
python -m pip install nebula5_python==5.3.0 fastapi==0.142.1 uvicorn==0.54.0
Python SDK 5.3.0 connects to NebulaGraph Database Developer Edition, FastAPI provides the application HTTP endpoint, and Uvicorn runs the application.
2. Configure the NebulaGraph Database Developer Edition connection¶
Choose the NebulaGraph Database Developer Edition address for your application computer:
| Application location | NebulaGraph Database Developer Edition address |
|---|---|
| On the same development machine as Docker | 127.0.0.1:9669 |
| On another computer | <development-machine-ip>:9669 |
Replace <development-machine-ip> with the IP address of the machine running Docker. If you changed the 9669:9669 mapping, use the development machine port to the left of the colon.
Set these environment variables in the terminal used to create the project. This example assumes that the application and Docker run on the same machine:
export NEBULA_ADDRESS='127.0.0.1:9669'
export NEBULA_USER='root'
export NEBULA_PASSWORD="$(python -c 'import getpass; print(getpass.getpass("Database password: "))')"
At the Database password: prompt, enter NebulaGraph Database Developer Edition's current, actual password. Enter a single $ for each character escaped as $$ in the Compose file. Use this terminal for the startup command below.
3. Save the application code¶
Download movie_sdk.py and save it in movie-sdk-app.
The example queries /default_schema/movie. If your data is in another Schema, update the graph path in both queries in the file.
View the complete code
"""Run with: python -m uvicorn movie_sdk:app --host 127.0.0.1 --port 8100."""
import logging
import os
from typing import Annotated
from fastapi import FastAPI, HTTPException, Path
from nebulagraph_python.client import NebulaClient
app = FastAPI(title="Movie cast service (Python SDK)")
logger = logging.getLogger(__name__)
address = os.environ.get("NEBULA_ADDRESS", "127.0.0.1:9669")
username = os.environ.get("NEBULA_USER", "root")
password = os.environ["NEBULA_PASSWORD"]
@app.get("/movies/{movie_id}/actors")
def get_actors(movie_id: Annotated[int, Path(ge=1, le=9223372036854775807)]):
# FastAPI validates the ID as an integer before this function runs.
# The graph path is fixed; neither query accepts arbitrary query text.
movie_query = (
"USE /default_schema/movie MATCH (m@Movie) "
f"WHERE m.id = {movie_id} RETURN m.name AS movie"
)
actors_query = (
"USE /default_schema/movie MATCH (a@Actor)-[:Act]->(m@Movie) "
f"WHERE m.id = {movie_id} "
"RETURN a.id AS actor_id, a.name AS actor ORDER BY actor_id"
)
client = None
try:
# A request owns its client and closes it before returning.
client = NebulaClient(
addresses=address,
user_name=username,
password=password,
connect_timeout_ms=3000,
request_timeout_ms=10000,
)
movies = list(client.execute(movie_query).as_primitive_by_row())
actors = (
list(client.execute(actors_query).as_primitive_by_row())
if movies
else []
)
except Exception as exc:
logger.warning("Database request failed (%s)", type(exc).__name__)
raise HTTPException(status_code=502, detail="Database request failed") from exc
finally:
if client is not None:
client.close()
if not movies:
raise HTTPException(status_code=404, detail="Movie not found")
return {"movie_id": movie_id, "movie": movies[0]["movie"], "actors": actors}
4. Start the application¶
From the project directory, with the virtual environment active, run:
python -m uvicorn movie_sdk:app --host 127.0.0.1 --port 8100
If port 8100 is in use, stop the application using it or use --port 8101 and update the URLs below.
These messages indicate that the application has started:
Application startup complete.
Uvicorn running on http://127.0.0.1:8100 (Press CTRL+C to quit)
Keep this terminal running. Use a browser or another terminal to call the endpoint. The application listens on local port 8100; press Ctrl+C to stop it.
5. Call the query endpoint¶
On the computer running the application, use either method below to find the movie with ID 3. If the application runs remotely over SSH, first set up port forwarding as described below.
- Open
http://127.0.0.1:8100/docs, the application's generated API documentation. - Expand
GET /movies/{movie_id}/actorsand select Try it out. - Enter
3formovie_id, select Execute, and inspect the response.
Open another terminal on the application computer and run:
curl -sS http://127.0.0.1:8100/movies/3/actors | python3 -m json.tool --no-ensure-ascii
Both methods should return the same JSON:
{
"movie_id": 3,
"movie": "Shadows in Paradise",
"actors": [
{"actor_id": 4826, "actor": "Matti Pellonpää"},
{"actor_id": 4828, "actor": "Sakari Kuosmanen"},
{"actor_id": 5999, "actor": "Kati Outinen"}
]
}
Access a remote application over SSH
In a local terminal on the browser computer, start forwarding. Replace <ssh-user> and <remote-address> with the values used to log in to the application computer:
ssh -N -L 8100:127.0.0.1:8100 <ssh-user>@<remote-address>
For a nondefault SSH port, add -p <ssh-port>. After authentication, the forwarding terminal keeps running, usually without output.
Keep the remote application terminal and local forwarding terminal running. Open http://127.0.0.1:8100/docs in the local browser. Requests to local port 8100 are forwarded to remote application port 8100.
If the local port is occupied, change the value after -L to 8101:127.0.0.1:8100 and open http://127.0.0.1:8101/docs instead.
The application uses port 8100; the NebulaGraph Database Developer Edition query port is 9669.
Code explanation¶
Query and response fields¶
The application queries the movie name first, then its actors. Checking the movie first distinguishes a missing movie from an existing movie with no actors: the former returns 404; the latter returns an empty actors array.
The actor query is:
USE /default_schema/movie MATCH (a@Actor)-[:Act]->(m@Movie)
WHERE m.id = 3
RETURN a.id AS actor_id, a.name AS actor ORDER BY actor_id
(a@Actor)-[:Act]->(m@Movie) matches an actor's relationship to a movie they act in. The arrow points from actor to movie. WHERE filters by movie ID, and ORDER BY sorts by actor ID. The application replaces 3 with the validated integer movie_id. See Example dataset: movie for the model.
| Query result | Application response field |
|---|---|
m.name AS movie in the movie query |
movie |
a.id AS actor_id in the actor query |
actor_id in each item of actors |
a.name AS actor in the actor query |
actor in each item of actors |
Connect and read results¶
This excerpt shows the NebulaGraph Database Developer Edition calls in the example. as_primitive_by_row() yields Python dictionaries, and list() collects the rows for FastAPI to return as JSON. finally closes the client whether a query succeeds or fails.
client = NebulaClient(addresses=address, user_name=username, password=password)
try:
movies = list(client.execute(movie_query).as_primitive_by_row())
actors = list(client.execute(actors_query).as_primitive_by_row()) if movies else []
finally:
client.close()
Each query specifies the graph with USE /default_schema/movie, so no session working graph is required. Pass Python multiline strings directly to execute(), without the ''' input delimiters used by ngql.
Response status and troubleshooting¶
| HTTP status | Meaning | Action |
|---|---|---|
200 |
The query succeeded | Inspect movie and actors; the array is empty when there are no actors |
404 |
No movie has this ID | Use an ID present in the dataset, such as 3 |
422 |
The movie ID has an invalid format or range | Use a positive integer no greater than 9223372036854775807 |
502 |
A NebulaGraph Database Developer Edition connection or query failed | Check the NebulaGraph Database Developer Edition address and password, then use Connect and run your first query to confirm access to the data |
To check input validation, run:
curl -i http://127.0.0.1:8100/movies/abc/actors
Expect HTTP 422. The request is rejected before any query is sent to NebulaGraph Database Developer Edition. The application accepts only a movie ID; the code fixes the query structure and graph path.
When the application is unreachable¶
If the browser reports ERR_CONNECTION_REFUSED or curl cannot connect, check:
- The application terminal is still running.
- The request targets the application computer.
127.0.0.1always refers to the computer running the browser or command. - For SSH access, the local forwarding terminal is still running.
Run this command on the browser computer. Use the updated local port if you changed it:
curl -I http://127.0.0.1:8100/docs
Expect HTTP/1.1 200 OK, confirming that the application's API documentation is reachable. Then call the movie endpoint to check the NebulaGraph Database Developer Edition query results.
The exception type is logged in the terminal running the application.
Exercise: query directors¶
Copy the get_actors() function and its @app.get decorator. Name the new function get_directors() and change its route to /movies/{movie_id}/directors. Keep the movie existence check and replace the actor query with:
USE /default_schema/movie MATCH (d@Director)-[:Direct]->(m@Movie)
WHERE m.id = 3
RETURN d.id AS director_id, d.name AS director ORDER BY director_id
Rename the actor query and result variables for directors. Return a directors array, with director_id and director in each item.
Restart the application and call the new endpoint to check the movie and its directors. An existing movie with no director relationships returns an empty directors array; a missing movie returns 404. Run the query in the NebulaGraph Studio console first to compare its result with the application response.
Continue development¶
Press Ctrl+C to stop the application. NebulaGraph Database Developer Edition continues running and retains its graph data. If you switch terminals before restarting, activate the virtual environment and set the environment variables again.
The example creates an SDK client for each request and closes it when the request ends. For your own service, design connection reuse and caller authentication around your concurrency and access requirements.
- Python SDK guide: Read about connections and result sets.
- FastAPI documentation: Learn about routing and application configuration.
- Build a service with the REST API: Implement the same endpoint over HTTP.