Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ user.attach(
target=company,
options={"type": "WORKS_AT", "direction": "out"}
)

# Run a raw Cypher query (cloud-only)
raw = db.query.raw({
"query": "MATCH (u:USER) RETURN u LIMIT $limit",
"params": {"limit": 5}
})
print(raw.get("data"))
```

## Pushing Nested JSON
Expand All @@ -97,6 +104,21 @@ db.records.create_many("COMPANY", {
})
```

## Importing CSV Data with Parse Config

```python
csv_data = """name,email,age\nJohn,john@example.com,30\nJane,jane@example.com,25"""

response = db.records.import_csv(
label="USER",
data=csv_data,
options={"returnResult": True, "suggestTypes": True},
parse_config={"header": True, "skipEmptyLines": True, "dynamicTyping": True}
)

print(response.get("data"))
```

## SearchResult API

RushDB Python SDK uses a modern `SearchResult` container that follows Python SDK best practices similar to boto3, google-cloud libraries, and other popular SDKs.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "rushdb"
version = "1.10.0"
version = "1.14.0"
description = "RushDB Python SDK"
authors = [
{name = "RushDB Team", email = "hi@rushdb.com"}
Expand Down
4 changes: 4 additions & 0 deletions src/rushdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Exposes the RushDB class.
"""

from .api.query import QueryAPI
from .api.relationships import RelationsAPI
from .client import RushDB
from .common import RushDBError
from .models.property import Property
Expand All @@ -21,4 +23,6 @@
"Property",
"RelationshipOptions",
"RelationshipDetachOptions",
"QueryAPI",
"RelationsAPI",
]
15 changes: 15 additions & 0 deletions src/rushdb/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .labels import LabelsAPI
from .properties import PropertiesAPI
from .query import QueryAPI
from .records import RecordsAPI
from .relationships import RelationsAPI
from .transactions import TransactionsAPI

__all__ = [
"RecordsAPI",
"PropertiesAPI",
"LabelsAPI",
"TransactionsAPI",
"QueryAPI",
"RelationsAPI",
]
41 changes: 41 additions & 0 deletions src/rushdb/api/query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Any, Dict, Optional

from ..models.transaction import Transaction
from .base import BaseAPI


class QueryAPI(BaseAPI):
"""API client for executing raw Cypher queries (cloud-only).

This endpoint is only available when using the RushDB managed cloud
service or when your project is connected to a custom database through
RushDB Cloud. It will not function for self-hosted or local-only deployments.

Example:
>>> from rushdb import RushDB
>>> db = RushDB("RUSHDB_API_KEY")
>>> result = db.query.raw({
... "query": "MATCH (n:Person) RETURN n LIMIT $limit",
... "params": {"limit": 10}
... })
>>> print(result)
"""

def raw(
self,
body: Dict[str, Any],
transaction: Optional[Transaction] = None,
) -> Dict[str, Any]:
"""Execute a raw Cypher query.

Args:
body (Dict[str, Any]): Payload containing:
- query (str): Cypher query string
- params (Optional[Dict[str, Any]]): Parameter dict
transaction (Optional[Transaction]): Optional transaction context.

Returns:
Dict[str, Any]: API response including raw driver result in 'data'.
"""
headers = Transaction._build_transaction_header(transaction)
return self.client._make_request("POST", "/query/raw", body, headers)
36 changes: 28 additions & 8 deletions src/rushdb/api/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ def import_csv(
label: str,
data: str,
options: Optional[Dict[str, bool]] = None,
parse_config: Optional[Dict[str, Any]] = None,
transaction: Optional[Transaction] = None,
) -> List[Dict[str, Any]]:
"""Import records from CSV data.
Expand All @@ -511,14 +512,17 @@ def import_csv(

Args:
label (str): The label/type to assign to all records created from the CSV.
data (Union[str, bytes]): The CSV content to import. Can be provided
as a string.
options (Optional[Dict[str, bool]], optional): Configuration options for the import operation.
Available options:
- returnResult (bool): Whether to return the created records data. Defaults to True.
- suggestTypes (bool): Whether to automatically suggest data types for CSV columns. Defaults to True.
transaction (Optional[Transaction], optional): Transaction context for the operation.
If provided, the operation will be part of the transaction. Defaults to None.
data (str): The CSV content to import as a string.
options (Optional[Dict[str, bool]]): Import write options (see create_many for list).
parse_config (Optional[Dict[str, Any]]): CSV parsing configuration keys (subset of PapaParse):
- delimiter (str)
- header (bool)
- skipEmptyLines (bool | 'greedy')
- dynamicTyping (bool)
- quoteChar (str)
- escapeChar (str)
- newline (str)
transaction (Optional[Transaction]): Transaction context for the operation.

Returns:
List[Dict[str, Any]]: List of dictionaries representing the imported records,
Expand All @@ -544,6 +548,22 @@ def import_csv(
"data": data,
"options": options or {"returnResult": True, "suggestTypes": True},
}
if parse_config:
# pass through only known parse config keys, ignore others silently
allowed_keys = {
"delimiter",
"header",
"skipEmptyLines",
"dynamicTyping",
"quoteChar",
"escapeChar",
"newline",
}
payload["parseConfig"] = {
k: v
for k, v in parse_config.items()
if k in allowed_keys and v is not None
}

return self.client._make_request(
"POST", "/records/import/csv", payload, headers
Expand Down
65 changes: 65 additions & 0 deletions src/rushdb/api/relationships.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,68 @@ async def find(
)

return response.data

def create_many(
self,
*,
source: dict,
target: dict,
type: Optional[str] = None,
direction: Optional[str] = None,
many_to_many: Optional[bool] = None,
transaction: Optional[Union[Transaction, str]] = None,
) -> dict:
"""Bulk create relationships by matching keys or many-to-many cartesian.

Modes:
1. Key-match (default): requires source.key and target.key, creates relationships where source[key] = target[key].
2. many_to_many=True: cartesian across filtered sets (requires where filters on both source & target and omits keys).

Args:
source (dict): { label: str, key?: str, where?: dict }
target (dict): { label: str, key?: str, where?: dict }
type (str, optional): Relationship type override.
direction (str, optional): 'in' | 'out'. Defaults to 'out' server-side when omitted.
many_to_many (bool, optional): Enable cartesian mode (requires filters, disallows keys).
transaction (Transaction|str, optional): Transaction context.

Returns:
dict: API response payload.
"""
headers = Transaction._build_transaction_header(transaction)
payload: dict = {"source": source, "target": target}
if type:
payload["type"] = type
if direction:
payload["direction"] = direction
if many_to_many is not None:
payload["manyToMany"] = many_to_many
return self.client._make_request(
"POST", "/relationships/create-many", payload, headers
)

def delete_many(
self,
*,
source: dict,
target: dict,
type: Optional[str] = None,
direction: Optional[str] = None,
many_to_many: Optional[bool] = None,
transaction: Optional[Union[Transaction, str]] = None,
) -> dict:
"""Bulk delete relationships using same contract as create_many.

See create_many for argument semantics.
"""
headers = Transaction._build_transaction_header(transaction)
payload: dict = {"source": source, "target": target}
if type:
payload["type"] = type
if direction:
payload["direction"] = direction
if many_to_many is not None:
payload["manyToMany"] = many_to_many
return self.client._make_request(
"POST", "/relationships/delete-many", payload, headers
)
4 changes: 4 additions & 0 deletions src/rushdb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

from .api.labels import LabelsAPI
from .api.properties import PropertiesAPI
from .api.query import QueryAPI
from .api.records import RecordsAPI
from .api.relationships import RelationsAPI
from .api.transactions import TransactionsAPI
from .common import RushDBError
from .utils.token_prefix import extract_mixed_properties_from_token
Expand Down Expand Up @@ -121,6 +123,8 @@ def __init__(self, api_key: str, base_url: Optional[str] = None):
self.properties = PropertiesAPI(self)
self.labels = LabelsAPI(self)
self.transactions = TransactionsAPI(self)
self.query = QueryAPI(self)
self.relationships = RelationsAPI(self)

def _make_request(
self,
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.