From 65c13b0a240c97f828765dbfff7e7dabc62f2469 Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Fri, 23 Oct 2026 21:15:11 +0900 Subject: [PATCH] docs: add the Antora component with the first slice of pages Adds docs/antora.yml (component arcadedb-python, unversioned) and a first slice of AsciiDoc pages: index, installation, quickstart and a how-to on databases and queries. The distribution and import names are AsciiDoc attributes in antora.yml. Adds *.adoc to the REUSE annotation. Co-Authored-By: Claude Opus 5.5 --- REUSE.toml | 2 +- docs/antora.yml | 17 ++ docs/modules/ROOT/nav.adoc | 5 + .../pages/how-to/databases-and-queries.adoc | 235 ++++++++++++++++++ docs/modules/ROOT/pages/index.adoc | 101 ++++++++ docs/modules/ROOT/pages/installation.adoc | 141 +++++++++++ docs/modules/ROOT/pages/quickstart.adoc | 143 +++++++++++ 7 files changed, 643 insertions(+), 1 deletion(-) create mode 100644 docs/antora.yml create mode 100644 docs/modules/ROOT/nav.adoc create mode 100644 docs/modules/ROOT/pages/how-to/databases-and-queries.adoc create mode 100644 docs/modules/ROOT/pages/index.adoc create mode 100644 docs/modules/ROOT/pages/installation.adoc create mode 100644 docs/modules/ROOT/pages/quickstart.adoc diff --git a/REUSE.toml b/REUSE.toml index 3edb173..e9eeb73 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -1,6 +1,6 @@ version = 1 [[annotations]] -path = ["**/*.py","**/*.sh","**/*.md","**/*.y*ml",".git*","scripts/Dockerfile.build","scripts/jar_exclusions.txt","pyproject.toml","setup.py","src/java/**/*","tests/fixtures/**"] +path = ["**/*.py","**/*.sh","**/*.md","**/*.adoc","**/*.y*ml",".git*","scripts/Dockerfile.build","scripts/jar_exclusions.txt","pyproject.toml","setup.py","src/java/**/*","tests/fixtures/**"] SPDX-FileCopyrightText = "Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com)" SPDX-License-Identifier = "Apache-2.0" diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 0000000..c362d5f --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,17 @@ +name: arcadedb-python +title: ArcadeDB Python +# Unversioned, so the component is published at /arcadedb-python/. +version: ~ +nav: + - modules/ROOT/nav.adoc +asciidoc: + attributes: + # Package names. After the rename of the distribution these are the only + # lines to change; every page takes the names from here. + pkg-name: arcadedb-embedded + import-name: arcadedb_embedded + import-stmt: import arcadedb_embedded as arcadedb + native-pkg-name: arcadedb-embedded-native + native-import-name: arcadedb_native + repo-url: https://github.com/ArcadeData/arcadedb-python + pypi-url: https://pypi.org/project/arcadedb-embedded/ diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc new file mode 100644 index 0000000..28708e8 --- /dev/null +++ b/docs/modules/ROOT/nav.adoc @@ -0,0 +1,5 @@ +* xref:index.adoc[ArcadeDB Python] +* xref:installation.adoc[Installation] +* xref:quickstart.adoc[Quickstart] +* How-to +** xref:how-to/databases-and-queries.adoc[Databases and queries] diff --git a/docs/modules/ROOT/pages/how-to/databases-and-queries.adoc b/docs/modules/ROOT/pages/how-to/databases-and-queries.adoc new file mode 100644 index 0000000..b064c1c --- /dev/null +++ b/docs/modules/ROOT/pages/how-to/databases-and-queries.adoc @@ -0,0 +1,235 @@ += Databases and queries +:description: Create, open, check and drop a database, then write and read it with SQL and openCypher. + +// Sources: bindings docs guide/core/database.md and guide/core/queries.md. + +This guide covers the life of a database directory and the two calls that talk to it. Prefer SQL or openCypher through `db.command(...)` and `db.query(...)` for schema, inserts and graph work. The bindings also expose wrapper objects for the rest of the Java API; they are not covered here. + +Every script on this page ran against the published `{pkg-name}` wheel. Run each one in an empty directory. + +== The life of a database + +A database is a directory on your filesystem. It is created, opened, used, closed and, if you want, dropped. + +[source,python,subs="attributes+"] +---- +{import-stmt} + +# Create (raises if the path already holds a database) +with arcadedb.create_database("./mydb") as db: + print(f"Created at: {db.get_database_path()}") + db.command("sql", "CREATE VERTEX TYPE User") + with db.transaction(): + db.command("sql", "INSERT INTO User SET name = ?", "Alice") + +print("Exists:", arcadedb.database_exists("./mydb")) + +# Creating it again fails +try: + with arcadedb.create_database("./mydb"): + pass +except Exception as e: + print(type(e).__name__, e, sep=": ") + +# Open, and read a value back +with arcadedb.open_database("./mydb") as db: + print("Name:", db.get_name()) + for user in db.query("sql", "SELECT FROM User"): + print(user.get("name")) + +# Open an existing database or create it +path = "./myapp" +if arcadedb.database_exists(path): + db = arcadedb.open_database(path) +else: + db = arcadedb.create_database(path) +with db: + print("Transaction active:", db.is_transaction_active()) + with db.transaction(): + print("Transaction active:", db.is_transaction_active()) + print("Transaction active:", db.is_transaction_active()) + +# Drop: deletes all files, irreversible +with arcadedb.open_database("./myapp") as db: + db.drop() +print("Exists after drop:", arcadedb.database_exists("./myapp")) +---- + +[source,text] +---- +Created at: ./mydb +Exists: True +ArcadeDBError: Failed to create database: com.arcadedb.exception.DatabaseOperationException: Database './mydb' already exists +Name: mydb +Alice +Transaction active: False +Transaction active: True +Transaction active: False +Exists after drop: False +---- + +Notes on the script: + +* `create_database(path)` creates the directory and opens the database for reading and writing. It raises `ArcadeDBError` if the path already holds a database, as the third output line shows. +* `open_database(path)` opens an existing database. `database_exists(path)` tells the two cases apart. +* `with` closes the database when the block ends, also when an exception leaves it. On close, active transactions roll back. You can call `db.close()` yourself, inside `try`/`finally`, but the context manager is the recommended way. +* `db.drop()` deletes all files of the database. It cannot be undone. +* `db.is_transaction_active()` is `True` only inside `with db.transaction():`. + +=== Locking + +The open database holds an operating-system lock on `database.lck` in its directory. A second process cannot open the same directory while the first holds it; open it in one process and share the handle between that process's threads. + +After a crash the operating system releases the lock and leaves the file behind. A leftover file blocks nothing: the next open sees it and replays the write-ahead log to recover. Do not delete it by hand, because that skips the recovery. + +=== Several databases, one JVM + +Each Python process starts one JVM, on the first database or server it creates. All databases of the process share it. To set JVM options, call `start_jvm(...)` before the first database; see xref:installation.adoc#jvm-options[JVM options]. + +== Write and query + +[source,python,subs="attributes+"] +---- +{import-stmt} + +with arcadedb.create_database("./qdb") as db: + db.command("sql", "CREATE VERTEX TYPE Person") + db.command("sql", "CREATE PROPERTY Person.name STRING") + db.command("sql", "CREATE PROPERTY Person.age INTEGER") + db.command("sql", "CREATE EDGE TYPE Knows") + + with db.transaction(): + db.command("sql", "INSERT INTO Person SET name = ?, age = ?", "Alice", 30) + db.command("sql", "INSERT INTO Person SET name = ?, age = ?", "Bob", 25) + db.command("sql", "INSERT INTO Person SET name = ?, age = ?", "Charlie", 35) + + # Positional parameters + for p in db.query("sql", "SELECT FROM Person WHERE name = ? AND age > ?", "Alice", 25): + print("positional:", p.get("name"), p.get("age")) + + # Named parameters + rows = db.query( + "sql", + "SELECT FROM Person WHERE name = :name AND age > :min_age", + {"name": "Alice", "min_age": 25}, + ) + print("named:", [r.get("name") for r in rows]) + + # Named collection parameter + rows = db.query( + "sql", + "SELECT FROM Person WHERE age IN :ages ORDER BY age", + {"ages": [25, 30]}, + ) + print("collection:", [r.get("name") for r in rows]) + + # first() and to_list() + first_person = db.query("sql", "SELECT FROM Person ORDER BY name").first() + print("first:", first_person.get("name")) + people = db.query("sql", "SELECT FROM Person ORDER BY name").to_list() + print("to_list:", len(people), people[1]["name"]) + missing = db.query("sql", "SELECT FROM Person WHERE name = ?", "Unknown").first() + print("no match:", missing) + + # Count a type + print("count_type:", db.count_type("Person")) + + # Update and delete in a transaction + with db.transaction(): + db.command("sql", "UPDATE Person SET age = ? WHERE name = ?", 26, "Bob") + db.command("sql", "DELETE FROM Person WHERE name = ?", "Charlie") + print("after write:", [(r.get("name"), r.get("age")) for r in db.query("sql", "SELECT FROM Person ORDER BY name")]) + + # SQLScript: several statements in one call + script = """ + CREATE VERTEX TYPE ScriptVertex; + INSERT INTO ScriptVertex SET name = 'test'; + ALTER TYPE ScriptVertex ALIASES sv; + """ + with db.transaction(): + result = db.command("sqlscript", script) + last = result.first() + print("sqlscript:", last.get("operation"), last.get("typeName")) + + # @rid-based pagination + last_rid = "#-1:-1" + batch_size = 1 + page_no = 0 + while True: + chunk = db.query( + "sql", + "SELECT @rid AS rid, name FROM Person WHERE @rid > ? LIMIT ?", + last_rid, + batch_size, + ).to_list() + if not chunk: + break + page_no += 1 + print("page", page_no, [c["name"] for c in chunk]) + last_rid = str(chunk[-1]["rid"]) + + # openCypher through the same two calls + with db.transaction(): + db.command( + "opencypher", + "MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}) CREATE (a)-[:Knows]->(b)", + ) + for row in db.query( + "opencypher", + "MATCH (a:Person)-[:Knows]->(b:Person) RETURN a.name AS a, b.name AS b", + ): + print("cypher:", row.get("a"), "knows", row.get("b")) + + # Aggregation + total = db.query("sql", "SELECT count(*) AS total FROM Person").first().get("total") + print("total:", total) +---- + +[source,text] +---- +positional: Alice 30 +named: ['Alice'] +collection: ['Bob', 'Alice'] +first: Alice +to_list: 3 Bob +no match: None +count_type: 3 +after write: [('Alice', 30), ('Bob', 26)] +sqlscript: ALTER TYPE ScriptVertex +page 1 ['Alice'] +page 2 ['Bob'] +cypher: Alice knows Bob +total: 2 +---- + +=== Parameters + +Bind values as parameters instead of pasting them into the statement text. A pasted value can change the statement (SQL injection), and a quote in a name breaks it. ArcadeDB also caches parsed statements by their text, so every distinct pasted value is parsed again. Identifiers such as type and property names cannot be bound and belong in the text. + +* Positional: one `?` per value, from the extra arguments (`db.query("sql", q, "Alice", 25)`) or from one list or tuple (`db.query("sql", q, ["Alice", 25])`). +* Named: `:name` in the statement and a dict as the one extra argument. A named parameter can hold a collection, as in `age IN :ages`. +* `None` binds as null. + +=== Results + +`db.query` returns a result set you can iterate. Two methods cover the usual needs: + +* `first()` returns the first row, or `None` when there is none, so `if db.query(...).first():` tests whether anything matched. +* `to_list()` materialises the whole result as a list of dicts. Use it when you need the whole result in memory; iterate when you stop early or the result is large. + +=== Pagination + +For a large type, page by `@rid` instead of `SKIP`. Each page is `WHERE @rid > ? LIMIT ?`, and the cursor is the `@rid` of the last row of the previous page. Start with `"#-1:-1"`. The script above shows the loop with a page size of one so that two short pages print. + +=== SQLScript + +`db.command("sqlscript", script)` runs several statements in one call. Without an explicit `RETURN`, the result set holds the last executed statement, DDL such as `CREATE` and `ALTER` included. That is why the script above reports the `ALTER TYPE` statement. + +=== openCypher + +The same two calls run openCypher: pass `"opencypher"` as the language. The script creates an edge with a `CREATE` pattern and reads it back with `MATCH`. + +== Next steps + +* xref:quickstart.adoc[Quickstart] +* xref:index.adoc[ArcadeDB Python] diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000..21b9871 --- /dev/null +++ b/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,101 @@ += ArcadeDB Python +:description: Run the ArcadeDB engine inside your Python process: install, first steps and how-to guides for the {pkg-name} package. + +// Sources: bindings docs index.md, api-access-methods.md and getting-started/installation.md, +// and the PyPI description of arcadedb-embedded-native. + +`{pkg-name}` runs the ArcadeDB engine inside your Python process. Calls cross into the engine through https://jpype.readthedocs.io/[JPype], so there is no network hop and no server to start. The wheel bundles its own Java runtime (Java 25), so you do not install Java. + +ArcadeDB is a multi-model database: graph, document, key/value, vector and time series data live in one database, and you query it with SQL or openCypher. The bindings expose the two calls you need for that, `db.command(...)` and `db.query(...)`. + +[source,python,subs="attributes+"] +---- +{import-stmt} + +with arcadedb.create_database("./mydb") as db: + db.command("sql", "CREATE DOCUMENT TYPE Person") + with db.transaction(): + db.command("sql", "INSERT INTO Person SET name = ?, age = ?", "Alice", 30) + for record in db.query("sql", "SELECT FROM Person WHERE age > 25"): + print(record.get("name")) +---- + +[source,text] +---- +Alice +---- + +This page is the map. xref:installation.adoc[Installation] and xref:quickstart.adoc[Quickstart] get you to a working database, and the xref:how-to/databases-and-queries.adoc[how-to guides] go deeper. + +[[embedded-vs-http]] +== Embedded bindings or the HTTP driver + +ArcadeDB reaches Python in two ways. Pick by where the engine should run. + +[cols="1,2,2"] +|=== +| | Embedded bindings (this component) | HTTP driver + +| Where the engine runs +| In your Python process +| In a separate ArcadeDB server + +| Who can reach the data +| One process at a time: the database directory is locked while it is open +| Any number of clients, local or remote + +| Network round trip +| None +| One per call + +| Setup +| `pip install {pkg-name}` +| A running server, plus `pip install arcadedb-driver` +|=== + +Choose the embedded bindings for a single script, a notebook, or an application with one Python process that owns its data. Choose the xref:arcadedb::how-to/connectivity/drivers/python-http.adoc[HTTP driver] when more than one process, another language, or a remote machine must reach the same database, or when the server must outlive your Python process. + +The two meet in one case. The bindings bundle an optional HTTP server with the Studio web UI. It is off until you call `create_server()`. The process that owns the database keeps direct access, and other processes connect over HTTP. That server lives and dies with your Python process. For a database that must outlive any one client, or for HA, replication and TLS, run the regular xref:arcadedb::index.adoc[ArcadeDB server] instead. + +== JVM build or native build + +Two packages ship for now. Both give you the same engine and the same API. + +[cols="1,3,3"] +|=== +| | `{pkg-name}` (default) | `{native-pkg-name}` (preview) + +| Runtime +| The engine on a bundled JVM, reached through JPype +| The engine compiled ahead of time with GraalVM Community Edition to a shared library, loaded with `ctypes`: no JVM and no JPype + +| Import name +| `{import-name}` +| `{native-import-name}` + +| Status +| The package these docs describe +| A preview of alpha quality. It is an experiment, not a release: do not use it for data you cannot lose + +| Install +| `pip install {pkg-name}` +| `pip install --pre {native-pkg-name}` +|=== + +The preview installs beside the default package. Its public API follows the one of `{import-name}`, so the pages in this component apply to both; the JVM-specific parts (JVM options, `jvm_kwargs`) do not apply to the native build. Check the preview's https://pypi.org/project/{native-pkg-name}/[PyPI page] for its current status before you rely on it. + +== Requirements + +* Python 3.11 to 3.14. The wheels' CI runs all four versions on every supported platform. +* Linux (x86_64 and ARM64), macOS on Apple Silicon, or Windows (x86_64). +* No Java installation. + +== Links + +* xref:installation.adoc[Installation] +* xref:quickstart.adoc[Quickstart] +* xref:how-to/databases-and-queries.adoc[Databases and queries] +* {pypi-url}[`{pkg-name}` on PyPI] +* {repo-url}[Source repository] and {repo-url}/issues[issue tracker] + +The bindings are licensed under Apache 2.0, like the ArcadeDB engine. diff --git a/docs/modules/ROOT/pages/installation.adoc b/docs/modules/ROOT/pages/installation.adoc new file mode 100644 index 0000000..ae370a8 --- /dev/null +++ b/docs/modules/ROOT/pages/installation.adoc @@ -0,0 +1,141 @@ += Installation +:description: Install the {pkg-name} wheel, check that it works, and set JVM options. + +// Sources: bindings docs getting-started/installation.md, the PyPI metadata of +// arcadedb-embedded and arcadedb-embedded-native. + +== Install + +[source,shell,subs="attributes+"] +---- +pip install {pkg-name} +---- + +Or with https://docs.astral.sh/uv/[uv]: + +[source,shell,subs="attributes+"] +---- +uv add {pkg-name} +---- + +The package is self-contained. It carries a Java 25 runtime for its platform, so you do not install Java. Its only Python dependency is `JPype1` 1.5.0 or later (the Java-Python bridge), which pip installs for you. + +== Requirements + +* Python 3.11 to 3.14. The wheels' CI runs all four versions on every supported platform. +* One of the four platforms below. + +Every wheel is platform-specific, built on a native runner of its platform, and carries a JRE for that platform, so pip needs no hint to pick the right one. + +[cols="2,3"] +|=== +| Platform | Wheel tag (Python 3.12) + +| Linux x86_64 +| `manylinux_2_34_x86_64` + +| Linux ARM64 +| `manylinux_2_34_aarch64` + +| macOS Apple Silicon +| `macosx_11_0_arm64` + +| Windows x86_64 +| `win_amd64` +|=== + +== What is included + +* The ArcadeDB engine with all models (graph, document, key/value, vector, time series) and both query languages, SQL and openCypher. +* Vector indexes, and data import for CSV, XML and ArcadeDB JSONL. +* The optional in-process HTTP server and the Studio web UI. They cost nothing at run time until you call `create_server()`. +* A bundled Java 25 runtime, trimmed with `jlink`. + +Engine modules the bindings do not use are left out to keep the wheel small. These include the gRPC and MongoDB wire protocols, Raft HA, and Gremlin. + +NOTE: *JavaScript is not available.* The GraalJS jars are left out, so `DEFINE FUNCTION ... LANGUAGE js` fails. Define functions in SQL or openCypher, or register a Python function with `register_function()`. + +== Verify the installation + +[source,python,subs="attributes+"] +---- +{import-stmt} +print(f"ArcadeDB Python bindings version: {arcadedb.__version__}") + +# Test database creation +with arcadedb.create_database("./test") as db: + result = db.query("sql", "SELECT 1 as test") + print(f"Database working: {result.first().get('test') == 1}") +---- + +Run in an empty directory, the script prints (the version matches what you installed): + +[source,text] +---- +ArcadeDB Python bindings version: 26.10.2.post1 +Database working: True +---- + +The engine also writes log lines to the standard error stream. The output on this page shows the standard output only. + +[[jvm-options]] +== JVM options + +Configure the bundled JVM inside Python, before the first database is created: + +[source,python,subs="attributes+"] +---- +{import-stmt} +from {import-name}.jvm import start_jvm + +# Configure the JVM once per process, before the first database is created +start_jvm(heap_size="2g", jvm_args="-XX:MaxDirectMemorySize=2g") + +with arcadedb.create_database("./jvmdb") as db: + print(db.query("sql", "SELECT 1 AS one").first().get("one")) +---- + +[source,text] +---- +1 +---- + +Or pass the options when you create or open a database: + +[source,python,subs="attributes+"] +---- +{import-stmt} + +with arcadedb.create_database("./jvmdb2", jvm_kwargs={"heap_size": "2g"}) as db: + print(db.query("sql", "SELECT 1 AS one").first().get("one")) +---- + +[source,text] +---- +1 +---- + +Two kinds of flag go in `jvm_args`: + +* `-X` and `-XX` flags are JVM runtime options, such as `-Xmx` for the maximum heap and `-XX:MaxDirectMemorySize=` for off-heap buffers. +* `-D` flags are system properties that configure ArcadeDB. + +WARNING: *One JVM configuration per process.* JVM options are locked after the JVM starts. Call `start_jvm(...)` or pass `jvm_kwargs` before the first database is created. To change the settings, start a new Python process. + +Each Python process starts its own JVM, and each JVM sees the whole container limit. With several Python processes in one container, give each process its own `heap_size`. + +== Native build (preview) + +`{native-pkg-name}` is the same engine compiled to a GraalVM native-image shared library. It needs no JVM and no JPype, so the JVM options above do not apply to it. It is a preview of alpha quality, and its versions are pre-releases: + +[source,shell,subs="attributes+"] +---- +pip install --pre {native-pkg-name} +---- + +It installs beside `{pkg-name}`: the distribution names differ, and so do the import names (`{native-import-name}` instead of `{import-name}`). Optional extras install the columnar helpers: `[numpy]`, `[arrow]`, `[pandas]` and `[polars]`. Report problems in the {repo-url}/issues[issue tracker] and say that you use the native wheel. + +== Next steps + +* xref:quickstart.adoc[Quickstart] +* xref:how-to/databases-and-queries.adoc[Databases and queries] diff --git a/docs/modules/ROOT/pages/quickstart.adoc b/docs/modules/ROOT/pages/quickstart.adoc new file mode 100644 index 0000000..188d5ca --- /dev/null +++ b/docs/modules/ROOT/pages/quickstart.adoc @@ -0,0 +1,143 @@ += Quickstart +:description: Create a database, insert records in a transaction and query them with SQL, in about five minutes. + +// Source: bindings docs getting-started/quickstart.md. + +This page builds a small database from scratch. You need the package from xref:installation.adoc[Installation]. Each script runs against the published `{pkg-name}` wheel; the output shown is what it printed. + +== Embedded mode + +The database runs in your Python process. You use SQL for the schema and for CRUD, through two calls: + +* `db.command(language, statement, *params)` runs a statement that can change data or schema. +* `db.query(language, statement, *params)` runs a read. + +Two rules cover most first mistakes: + +* Use a `with` block. It closes the database when the block ends, also when an exception leaves it. +* *Writes need a transaction.* `INSERT`, `UPDATE` and `DELETE` go inside `with db.transaction():`. Schema statements apply immediately. Reads need no transaction. + +== A complete example + +[source,python,subs="attributes+"] +---- +{import-stmt} + + +def main(): + # Create database + with arcadedb.create_database("./quickstart") as db: + # Create schema (SQL) + db.command("sql", "CREATE DOCUMENT TYPE Person") + db.command("sql", "CREATE PROPERTY Person.name STRING") + db.command("sql", "CREATE PROPERTY Person.age INTEGER") + db.command("sql", "CREATE PROPERTY Person.email STRING") + db.command("sql", "CREATE INDEX ON Person (name) NOTUNIQUE") + + # Insert data (in a transaction) + with db.transaction(): + for name, age, email in [ + ("Alice", 30, "alice@example.com"), + ("Bob", 25, "bob@example.com"), + ("Charlie", 35, "charlie@example.com"), + ]: + db.command( + "sql", + "INSERT INTO Person SET name = ?, age = ?, email = ?", + name, + age, + email, + ) + print("Inserted 3 records") + + # Query all (no transaction needed for reads) + print("All people:") + for record in db.query("sql", "SELECT FROM Person ORDER BY age"): + print(f" - {record.get('name')}, age {record.get('age')}") + + # Query with a filter + print("People over 25:") + for record in db.query("sql", "SELECT FROM Person WHERE age > 25 ORDER BY age"): + print(f" - {record.get('name')}, age {record.get('age')}") + + # Count + total = db.query("sql", "SELECT count(*) as total FROM Person").first().get("total") + print("Total people:", total) + + +if __name__ == "__main__": + main() +---- + +[source,text] +---- +Inserted 3 records +All people: + - Bob, age 25 + - Alice, age 30 + - Charlie, age 35 +People over 25: + - Alice, age 30 + - Charlie, age 35 +Total people: 3 +---- + +`create_database` raises if the path already holds a database, so run the script in an empty directory or delete `./quickstart` before you run it again. + +The steps in the script are: + +. `create_database("./quickstart")` creates the directory and opens a database in it. +. Four `CREATE` statements define a document type `Person`, three properties and an index on `name`. +. A transaction inserts three records. The `?` marks are positional parameters bound to the values after the statement. +. `db.query` reads them back, with and without a filter, and `count(*)` counts them. + +== Reopen the database + +Open an existing database with `open_database`. The second part of the script shows what happens when a write runs outside a transaction: + +[source,python,subs="attributes+"] +---- +{import-stmt} + +# Open the database that quickstart.py created +with arcadedb.open_database("./quickstart") as db: + result = db.query("sql", "SELECT FROM Person") + print(f"Found {len(list(result))} records") + +# A write outside a transaction raises +with arcadedb.open_database("./quickstart") as db: + try: + db.command("sql", "INSERT INTO Person SET name = 'Dora'") + except Exception as e: + print(type(e).__name__, e, sep=": ") +---- + +[source,text] +---- +Found 3 records +ArcadeDBError: Command failed: com.arcadedb.exception.TransactionException: Transaction not begun +---- + +== Server mode + +When another process, another language or a browser needs the same data, start the bundled server. The process that created it keeps full-speed embedded access, and `get_studio_url()` returns the address of the Studio UI: + +[source,python,subs="attributes+"] +---- +{import-stmt} + +with arcadedb.create_server("./databases", root_password="password123") as server: + db = server.create_database("mydb") # embedded access, in this process + print("Studio:", server.get_studio_url()) # HTTP access, any process +---- + +[source,text] +---- +Studio: http://localhost:2480/ +---- + +The server runs only while your Python process runs. See xref:index.adoc#embedded-vs-http[Embedded bindings or the HTTP driver] for when to run the regular ArcadeDB server instead. + +== Next steps + +* xref:how-to/databases-and-queries.adoc[Databases and queries]: open, drop and check databases, parameters, pagination, SQLScript and openCypher.