# Quickstart This page connects to a server, reads vertices, edges and paths, writes to the graph, and joins graph and relational data in one transaction. It takes about ten minutes. It uses the small film graph that every page of this manual uses, `moviekb`: 18 films, 28 people, and the edges between them. Load it first by running the statements on the AgensGraph manual's [example dataset](https://tech.skaiworldwide.com/docs/en/agensgraph/18/get_started/example-dataset.html) page, in `psql` or any client. Then work down this page in a Python session. ## Connect ```python import agensgraph conn = agensgraph.connect("host=localhost dbname=docs user=agens") conn.graph("moviekb") conn.commit() ``` `connect()` takes a libpq connection string, the same one `psql` takes, or the same settings as keyword arguments (`agensgraph.connect(host="localhost", dbname="docs")`). It returns a `Connection`, which is a psycopg 3 connection with the graph types added, so anything you know about psycopg applies to it. `conn.graph("moviekb")` selects the graph that unqualified label names belong to. It is the driver's form of `SET graph_path = moviekb`, and it also reads the graph's labels so that values can be decoded in either of the server's renderings. Selecting a graph is per session: a new connection starts with none, unless the database or the role has a default. The `commit()` matters. Like every statement on a psycopg connection, selecting the graph opens a transaction, and a later `rollback()` would undo it along with everything else. [Connecting](../guide/connecting.md#selecting-a-graph) explains this and the other ways to select a graph. ## Read a vertex ```pycon >>> result = conn.execute_query("MATCH (m:movie {title: 'The Matrix'}) RETURN m") >>> result.keys ['m'] >>> (movie,) = result.records[0] >>> movie Vertex(movie[3.1]) >>> movie.label, movie.id ('movie', GraphId(labid=3, locid=1)) >>> movie.properties["year"], movie.properties["genre"] (1999, 'Action') ``` `execute_query()` runs one statement and reads the whole result. It returns a `Result`, whose `records` are a list of tuples, one per row, and whose `keys` are the column names. A vertex arrives as a `Vertex`, not as text to parse. `id` is its identity, a `GraphId` that prints the way the server prints it, `3.1`. `properties` is an ordinary `dict`, decoded from the property map the first time you read it. ## Read plain values Return properties instead of whole elements and each one arrives as the Python type of its JSON value: ```pycon >>> result = conn.execute_query( ... "MATCH (m:movie) WHERE m.year < 1990" ... " RETURN m.title AS title, m.year AS year ORDER BY year" ... ) >>> result.records [('Blade Runner', 1982), ('The Terminator', 1984)] ``` The title is a `str` and the year an `int`. In `psql` the same query prints `"Blade Runner"` with its JSON quotes, because a property comes back as `jsonb`; the driver decodes it for you. ## Pass parameters Never format values into the statement text. Pass them as parameters, with `%s` placeholders: ```pycon >>> conn.execute_query( ... "MATCH (p:person) WHERE p.born >= %s AND p.born < %s RETURN p.name ORDER BY p.name", ... (1978, 1981), ... ).records [('Neill Blomkamp',), ('Ryan Gosling',)] ``` A Python `str` is sent as `text`, an `int` as an integer, a `dict` as `jsonb`, and so on. [Parameters](../guide/parameters.md) has the details, including the positions in a Cypher statement where a parameter is not allowed. ## Follow an edge ```pycon >>> result = conn.execute_query( ... "MATCH (p:person)-[r:acted_in]->(m:movie {title: 'The Matrix'})" ... " RETURN p, r ORDER BY p.name LIMIT 1" ... ) >>> person, role = result.records[0] >>> person.properties["name"] 'Carrie-Anne Moss' >>> role Edge(acted_in[5.7] 4.3->3.1) >>> role.properties["role"] 'Trinity' >>> role.start == person.id True ``` An `Edge` has a `label`, an `id` and `properties` like a vertex, plus `start` and `end`, the identities of the two vertices it joins. ## Return a path ```pycon >>> (path,) = conn.execute_query( ... "MATCH p = (:movie {title: 'The Matrix'})-[:related_to*2]->(:movie)" ... " RETURN p ORDER BY length(p) LIMIT 1" ... ).records[0] >>> path Path(3 vertices, 2 edges) >>> path.length 2 >>> [v.properties["title"] for v in path.vertices] ['The Matrix', 'The Matrix Reloaded', 'The Matrix Revolutions'] ``` A `Path` holds its vertices and edges in order. `path.length` is the number of hops, and iterating over a path gives vertex, edge, vertex, and so on. ## Write ```pycon >>> result = conn.execute_query( ... "CREATE (:movie %s)", ... ({"title": "Dune", "year": 2021, "genre": "Sci-Fi"},), ... counts_=True, ... ) >>> result.counts.inserted_vertices 1 >>> conn.commit() ``` A property map is passed as a `dict` and goes to the server as `jsonb`. `counts_=True` asks for what the statement changed; without it the counters are not read and are all `None`. A statement outside a transaction opens one, as everywhere in psycopg, so nothing is visible to anyone else until you call `commit()`. Use the connection as a context manager to commit on success and roll back on an exception: ```python with agensgraph.connect("host=localhost dbname=docs user=agens") as other: other.graph("moviekb") other.execute_query( "MATCH (m:movie {title: 'Dune'}) SET m.director = %s", ("Denis Villeneuve",) ) # committed here, because the block ended without an exception ``` ## Combine SQL and Cypher The example dataset has an ordinary table, `rating`, next to the graph. A Cypher query and a SQL query run on the same connection and in the same transaction, and one statement can contain both: ```pycon >>> conn.execute_query(""" ... SELECT r.title, r.score ... FROM (MATCH (p:person {name: 'Keanu Reeves'})-[:acted_in]->(m:movie) ... RETURN m.title AS title) AS g ... JOIN rating r ON r.title = g.title #>> '{}' AND r.source = 'critics' ... ORDER BY r.score DESC ... """).records [('The Matrix', Decimal('8.7')), ('The Matrix Reloaded', Decimal('7.2')), ('The Matrix Revolutions', Decimal('6.7')), ('The Matrix Resurrections', Decimal('5.7'))] ``` The Cypher query sits where a subquery goes. Its `title` column is `jsonb`, so the outer query unwraps it with `#>> '{}'` before comparing it with the table's `text` column. Unwrap it out here, in SQL: a cast written inside the Cypher `RETURN` is boxed back into `jsonb` by the projection. The `score` column is `numeric` and arrives as a `Decimal`, as it would from any PostgreSQL query. ## The same in awaiting code `AsyncConnection` has the same methods, awaited: ```python import asyncio import agensgraph async def main(): dsn = "host=localhost dbname=docs user=agens" async with await agensgraph.AsyncConnection.connect(dsn) as conn: await conn.graph("moviekb") result = await conn.execute_query("MATCH (p:person) RETURN count(p)") print(result.records) asyncio.run(main()) ``` ```text [(28,)] ``` ## Close the connection ```python conn.close() ``` Closing a connection rolls back whatever it had not committed. The `with` form closes it for you. ## Where to go next - [Connecting](../guide/connecting.md) covers connection settings, selecting a graph, and the awaiting interface in full. - [Graph values](../guide/graph-values.md) explains vertices, edges, paths and identities. - [Bulk loading](../data/bulk-loading.md) is the fast way to write many elements. - [Pooling](../production/pooling.md) is how a service should hold its connections.