Source: https://dev-docs.nebula-graph.io/v5.3.2/app-development/python-sdk/ Documentation version: v5.3.2 # 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](https://dev-docs.nebula-graph.io/v5.3.2/app-development/#prepare-database) to start NebulaGraph Database Developer Edition and load the `movie` sample 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 | `:9669` Replace `` 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](https://dev-docs.nebula-graph.io/v5.3.2/app-development/files/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](https://dev-docs.nebula-graph.io/v5.3.2/app-development/python-sdk/#ssh-access) as described below. Browsercurl 1. Open `http://127.0.0.1:8100/docs`, the application's generated API documentation. 2. Expand `GET /movies/{movie_id}/actors` and select Try it out. 3. Enter `3` for `movie_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 `` and `` with the values used to log in to the application computer: ```` ssh -N -L 8100:127.0.0.1:8100 @ ```` For a nondefault SSH port, add `-p `. 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](https://dev-docs.nebula-graph.io/v5.3.2/get-started/dataset/) 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](https://dev-docs.nebula-graph.io/v5.3.2/get-started/first-query/#select-graph) 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.1` always 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](https://dev-docs.nebula-graph.io/v5.3.2/clients/python-client/): Read about connections and result sets. - [FastAPI documentation](https://fastapi.tiangolo.com/tutorial/first-steps/): Learn about routing and application configuration. - [Build a service with the REST API](https://dev-docs.nebula-graph.io/v5.3.2/app-development/rest-api/): Implement the same endpoint over HTTP.