diff --git a/README.md b/README.md index 39a5285..66ef82c 100644 --- a/README.md +++ b/README.md @@ -3,219 +3,123 @@ ![RushDB Logo](https://raw.githubusercontent.com/rush-db/rushdb/main/rushdb-logo.svg) # RushDB Python SDK -![PyPI - Version](https://img.shields.io/pypi/v/rushdb) +![PyPI - Version](https://img.shields.io/pypi/v/rushdb) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/rushdb) - ![PyPI - License](https://img.shields.io/pypi/l/rushdb) RushDB is an instant database for modern apps and DS/ML ops built on top of Neo4j. +It automates data normalization, manages relationships, and infers data types. -It automates data normalization, manages relationships, and infers data types, enabling developers to focus on building features rather than wrestling with data. +[📖 Documentation](https://docs.rushdb.com/python-sdk/introduction) • [🌐 Website](https://rushdb.com) • [☁️ Cloud Platform](https://app.rushdb.com) -[🌐 Homepage](https://rushdb.com) — [📢 Blog](https://rushdb.com/blog) — [☁️ Platform ](https://app.rushdb.com) — [📚 Docs](https://docs.rushdb.com/python-sdk/records-api) — [🧑‍💻 Examples](https://github.com/rush-db/examples) ---- - ## Installation -Install the RushDB Python SDK via pip: - - ```sh pip install rushdb ``` ---- - -## Usage - -### **1. Setup SDK** +## Quick Start ```python from rushdb import RushDB -db = RushDB("API_TOKEN", base_url="https://api.rushdb.com") -``` +# Initialize the client +db = RushDB("YOUR_API_TOKEN") ---- +# Create a record +user = db.records.create( + label="USER", + data={ + "name": "John Doe", + "email": "john@example.com", + "age": 30 + } +) -### **2. Push any JSON data** +# Find records +results = db.records.find({ + "where": { + "age": {"$gte": 18}, + "name": {"$startsWith": "J"} + }, + "limit": 10 +}) +# Create relationships +company = db.records.create( + label="COMPANY", + data={"name": "Acme Inc."} +) + +# Attach records with a relationship +user.attach( + target=company, + options={"type": "WORKS_AT", "direction": "out"} +) +``` + +## Pushing Nested JSON + +RushDB automatically normalizes nested objects into a graph structure: ```python -company_data = { +# Push nested JSON with automatic relationship creation +db.records.create_many("COMPANY", { "name": "Google LLC", - "address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA", - "foundedAt": "1998-09-04T00:00:00.000Z", "rating": 4.9, "DEPARTMENT": [{ "name": "Research & Development", - "description": "Innovating and creating advanced technologies for AI, cloud computing, and consumer devices.", "PROJECT": [{ "name": "Bard AI", - "description": "A state-of-the-art generative AI model for natural language understanding and creation.", - "active": True, - "budget": 1200000000, "EMPLOYEE": [{ "name": "Jeff Dean", - "position": "Head of AI Research", - "email": "jeff@google.com", - "dob": "1968-07-16T00:00:00.000Z", - "salary": 3000000 + "position": "Head of AI Research" }] }] }] -} - -db.records.create_many("COMPANY", company_data) +}) ``` -This operation will create 4 Records with proper data types and relationships according to this structure: +## Complete Documentation -```cypher -(Record:COMPANY) - -[r0:RUSHDB_DEFAULT_RELATION]-> - (Record:DEPARTMENT) - -[r1:RUSHDB_DEFAULT_RELATION]-> - (Record:PROJECT) - -[r2:RUSHDB_DEFAULT_RELATION]-> - (Record:EMPLOYEE) -``` +For comprehensive documentation, tutorials, and examples, please visit: ---- +**[docs.rushdb.com/python-sdk](https://docs.rushdb.com/python-sdk/introduction)** -### **3. Find Records by specific criteria** +Documentation includes: -```python -query = { - "labels": ["EMPLOYEE"], - "where": { - "position": {"$contains": "AI"}, - "PROJECT": { - "DEPARTMENT": { - "COMPANY": { - "rating": {"$gte": 4} - } - } - } - } -} +- Complete Records API reference +- Relationship management +- Complex query examples +- Transaction usage +- Vector search capabilities +- Data import tools -matched_employees = db.records.find(query) +## Support -company = db.records.find_uniq("COMPANY", {"where": {"name": "Google LLC"}}) -``` +- [GitHub Issues](https://github.com/rush-db/rushdb-python/issues) - Bug reports and feature requests +- [Discord Community](https://discord.gg/rushdb) - Get help from the community +- [Email Support](mailto:support@rushdb.com) - Direct support from the RushDB team --- - -# Documentation - -# RecordsAPI Documentation - -The `RecordsAPI` class provides methods for managing records in RushDB. It handles record creation, updates, deletion, searching, and relationship management. - -## Methods - -### create() - -Creates a new record in RushDB. - -**Signature:** -```python -def create( - self, - label: str, - data: Dict[str, Any], - options: Optional[Dict[str, bool]] = None, - transaction: Optional[Transaction] = None -) -> Record -``` - -**Arguments:** -- `label` (str): Label for the record -- `data` (Dict[str, Any]): Record data -- `options` (Optional[Dict[str, bool]]): Optional parsing and response options - - `returnResult` (bool): Whether to return the created record - - `suggestTypes` (bool): Whether to suggest property types -- `transaction` (Optional[Transaction]): Optional transaction object - -**Returns:** -- `Record`: Created record object - -**Example:** -```python -# Create a new company record -data = { - "name": "Google LLC", - "address": "1600 Amphitheatre Parkway", - "foundedAt": "1998-09-04T00:00:00.000Z", - "rating": 4.9 -} - -record = db.records.create( - label="COMPANY", - data=data, - options={"returnResult": True, "suggestTypes": True} -) -``` - -### create_many() - -Creates multiple records in a single operation. - -**Signature:** -```python -def create_many( - self, - label: str, - data: Union[Dict[str, Any], List[Dict[str, Any]]], - options: Optional[Dict[str, bool]] = None, - transaction: Optional[Transaction] = None -) -> List[Record] -``` - -**Arguments:** -- `label` (str): Label for all records -- `data` (Union[Dict[str, Any], List[Dict[str, Any]]]): List or Dict of record data -- `options` (Optional[Dict[str, bool]]): Optional parsing and response options -- `transaction` (Optional[Transaction]): Optional transaction object - -**Returns:** -- `List[Record]`: List of created record objects - -**Example:** -```python -# Create multiple company records -data = [ - { - "name": "Apple Inc", - "address": "One Apple Park Way", - "foundedAt": "1976-04-01T00:00:00.000Z", - "rating": 4.8 - }, - { - "name": "Microsoft Corporation", - "address": "One Microsoft Way", - "foundedAt": "1975-04-04T00:00:00.000Z", - "rating": 4.7 - } -] - -records = db.records.create_many( - label="COMPANY", - data=data, - options={"returnResult": True, "suggestTypes": True} -) -``` +
+

+ + View Documentation + +

+
### set() Updates a record by ID, replacing all data. **Signature:** + ```python def set( self, @@ -226,14 +130,17 @@ def set( ``` **Arguments:** + - `record_id` (str): ID of the record to update - `data` (Dict[str, Any]): New record data - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python # Update entire record data new_data = { @@ -252,6 +159,7 @@ response = db.records.set( Updates specific fields of a record by ID. **Signature:** + ```python def update( self, @@ -262,14 +170,17 @@ def update( ``` **Arguments:** + - `record_id` (str): ID of the record to update - `data` (Dict[str, Any]): Partial record data to update - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python # Update specific fields updates = { @@ -288,6 +199,7 @@ response = db.records.update( Searches for records matching specified criteria. **Signature:** + ```python def find( self, @@ -298,14 +210,17 @@ def find( ``` **Arguments:** + - `query` (Optional[SearchQuery]): Search query parameters - `record_id` (Optional[str]): Optional record ID to search from - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `List[Record]`: List of matching records **Example:** + ```python # Search for records with complex criteria query = { @@ -328,6 +243,7 @@ records = db.records.find(query=query) Deletes records matching a query. **Signature:** + ```python def delete( self, @@ -337,13 +253,16 @@ def delete( ``` **Arguments:** + - `query` (SearchQuery): Query to match records for deletion - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python # Delete records matching criteria query = { @@ -361,6 +280,7 @@ response = db.records.delete(query) Deletes one or more records by ID. **Signature:** + ```python def delete_by_id( self, @@ -370,13 +290,16 @@ def delete_by_id( ``` **Arguments:** + - `id_or_ids` (Union[str, List[str]]): Single ID or list of IDs to delete - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python # Delete single record response = db.records.delete_by_id("record-123") @@ -394,6 +317,7 @@ response = db.records.delete_by_id([ Creates relationships between records. **Signature:** + ```python def attach( self, @@ -405,6 +329,7 @@ def attach( ``` **Arguments:** + - `source` (Union[str, Dict[str, Any]]): Source record ID or data - `target` (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): Target record(s) - `options` (Optional[RelationshipOptions]): Relationship options @@ -413,9 +338,11 @@ def attach( - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python # Create relationship between records options = RelationshipOptions( @@ -435,6 +362,7 @@ response = db.records.attach( Removes relationships between records. **Signature:** + ```python def detach( self, @@ -446,6 +374,7 @@ def detach( ``` **Arguments:** + - `source` (Union[str, Dict[str, Any]]): Source record ID or data - `target` (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): Target record(s) - `options` (Optional[RelationshipDetachOptions]): Detach options @@ -454,9 +383,11 @@ def detach( - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python # Remove relationships between records options = RelationshipDetachOptions( @@ -476,6 +407,7 @@ response = db.records.detach( Imports records from CSV data. **Signature:** + ```python def import_csv( self, @@ -487,15 +419,18 @@ def import_csv( ``` **Arguments:** + - `label` (str): Label for imported records - `csv_data` (Union[str, bytes]): CSV data to import - `options` (Optional[Dict[str, bool]]): Import options - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `List[Dict[str, Any]]`: Imported records data **Example:** + ```python # Import records from CSV csv_data = """name,age,department,role @@ -532,6 +467,7 @@ Gets the record's unique identifier. **Type:** `str` **Example:** + ```python record = db.records.create("USER", {"name": "John"}) print(record.id) # e.g., "1234abcd-5678-..." @@ -544,6 +480,7 @@ Gets the record's property types. **Type:** `str` **Example:** + ```python record = db.records.create("USER", {"name": "John", "age": 25}) print(record.proptypes) # Returns property type definitions @@ -556,6 +493,7 @@ Gets the record's label. **Type:** `str` **Example:** + ```python record = db.records.create("USER", {"name": "John"}) print(record.label) # "USER" @@ -568,6 +506,7 @@ Gets the record's creation timestamp from its ID. **Type:** `int` **Example:** + ```python record = db.records.create("USER", {"name": "John"}) print(record.timestamp) # Unix timestamp in milliseconds @@ -580,6 +519,7 @@ Gets the record's creation date. **Type:** `datetime` **Example:** + ```python record = db.records.create("USER", {"name": "John"}) print(record.date) # datetime object @@ -592,6 +532,7 @@ print(record.date) # datetime object Updates all data for the record. **Signature:** + ```python def set( self, @@ -601,13 +542,16 @@ def set( ``` **Arguments:** + - `data` (Dict[str, Any]): New record data - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python record = db.records.create("USER", {"name": "John"}) response = record.set({ @@ -622,6 +566,7 @@ response = record.set({ Updates specific fields of the record. **Signature:** + ```python def update( self, @@ -631,13 +576,16 @@ def update( ``` **Arguments:** + - `data` (Dict[str, Any]): Partial record data to update - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python record = db.records.create("USER", { "name": "John", @@ -653,6 +601,7 @@ response = record.update({ Creates relationships with other records. **Signature:** + ```python def attach( self, @@ -663,6 +612,7 @@ def attach( ``` **Arguments:** + - `target` (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): Target record(s) - `options` (Optional[RelationshipOptions]): Relationship options - `direction` (Optional[Literal["in", "out"]]): Relationship direction @@ -670,9 +620,11 @@ def attach( - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python # Create two records user = db.records.create("USER", {"name": "John"}) @@ -693,6 +645,7 @@ response = user.attach( Removes relationships with other records. **Signature:** + ```python def detach( self, @@ -703,6 +656,7 @@ def detach( ``` **Arguments:** + - `target` (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): Target record(s) - `options` (Optional[RelationshipDetachOptions]): Detach options - `direction` (Optional[Literal["in", "out"]]): Relationship direction @@ -710,9 +664,11 @@ def detach( - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python # Detach user from group response = user.detach( @@ -729,6 +685,7 @@ response = user.detach( Deletes the record. **Signature:** + ```python def delete( self, @@ -737,12 +694,15 @@ def delete( ``` **Arguments:** + - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Dict[str, str]`: Response data **Example:** + ```python user = db.records.create("USER", {"name": "John"}) response = user.delete() @@ -823,26 +783,26 @@ with db.transactions.begin() as transaction: {"name": "John Doe"}, transaction=transaction ) - + # Update user user.update( {"status": "active"}, transaction=transaction ) - + # Create and attach department dept = db.records.create( "DEPARTMENT", {"name": "Engineering"}, transaction=transaction ) - + user.attach( target=dept, options=RelationshipOptions(type="BELONGS_TO"), transaction=transaction ) - + # Transaction will automatically commit if no errors occur # If an error occurs, it will automatically rollback ``` @@ -866,6 +826,7 @@ class PropertiesAPI(BaseAPI): Retrieves a list of properties based on optional search criteria. **Signature:** + ```python def find( self, @@ -875,16 +836,19 @@ def find( ``` **Arguments:** + - `query` (Optional[SearchQuery]): Search query parameters for filtering properties - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `List[Property]`: List of properties matching the search criteria **Example:** + ```python # Find all properties -properties = client.properties.find() +properties = db.properties.find() # Find properties with specific criteria query = { @@ -894,7 +858,7 @@ query = { }, "limit": 10 # Limit to 10 results } -filtered_properties = client.properties.find(query) +filtered_properties = db.properties.find(query) ``` ### find_by_id() @@ -902,6 +866,7 @@ filtered_properties = client.properties.find(query) Retrieves a specific property by its ID. **Signature:** + ```python def find_by_id( self, @@ -911,16 +876,19 @@ def find_by_id( ``` **Arguments:** + - `property_id` (str): Unique identifier of the property - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `Property`: Property details **Example:** + ```python # Retrieve a specific property by ID -property_details = client.properties.find_by_id("prop_123456") +property_details = db.properties.find_by_id("prop_123456") ``` ### delete() @@ -928,6 +896,7 @@ property_details = client.properties.find_by_id("prop_123456") Deletes a property by its ID. **Signature:** + ```python def delete( self, @@ -937,16 +906,19 @@ def delete( ``` **Arguments:** + - `property_id` (str): Unique identifier of the property to delete - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `None` **Example:** + ```python # Delete a property -client.properties.delete("prop_123456") +db.properties.delete("prop_123456") ``` ### values() @@ -954,6 +926,7 @@ client.properties.delete("prop_123456") Retrieves values for a specific property with optional sorting and pagination. **Signature:** + ```python def values( self, @@ -966,6 +939,7 @@ def values( ``` **Arguments:** + - `property_id` (str): Unique identifier of the property - `sort` (Optional[Literal["asc", "desc"]]): Sort order of values - `skip` (Optional[int]): Number of values to skip (for pagination) @@ -973,12 +947,14 @@ def values( - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** + - `PropertyValuesData`: Property values data, including optional min/max and list of values **Example:** + ```python # Get property values -values_data = client.properties.values( +values_data = db.properties.values( property_id="prop_age", sort="desc", # Sort values in descending order skip=0, # Start from the first value @@ -995,7 +971,7 @@ print(values_data.get('max')) # Maximum value (for numeric properties) ```python # Find all properties -all_properties = client.properties.find() +all_properties = db.properties.find() for prop in all_properties: print(f"Property ID: {prop['id']}") print(f"Name: {prop['name']}") @@ -1011,12 +987,12 @@ query = { }, "limit": 5 # Limit to 5 results } -numeric_score_properties = client.properties.find(query) +numeric_score_properties = db.properties.find(query) # Get values for a specific property if numeric_score_properties: first_prop = numeric_score_properties[0] - prop_values = client.properties.values( + prop_values = db.properties.values( property_id=first_prop['id'], sort="desc", limit=50 @@ -1024,15 +1000,16 @@ if numeric_score_properties: print(f"Values for {first_prop['name']}:") print(f"Min: {prop_values.get('min')}") print(f"Max: {prop_values.get('max')}") - + # Detailed property examination - detailed_prop = client.properties.find_by_id(first_prop['id']) + detailed_prop = db.properties.find_by_id(first_prop['id']) print("Detailed Property Info:", detailed_prop) ``` ## Property Types and Structures RushDB supports the following property types: + - `"boolean"`: True/False values - `"datetime"`: Date and time values - `"null"`: Null/empty values @@ -1040,6 +1017,7 @@ RushDB supports the following property types: - `"string"`: Text values ### Property Structure Example + ```python property = { "id": "prop_unique_id", @@ -1062,14 +1040,14 @@ Properties API methods support optional transactions for atomic operations: ```python # Using a transaction -with client.transactions.begin() as transaction: +with db.transactions.begin() as transaction: # Perform multiple property-related operations - property_to_delete = client.properties.find( + property_to_delete = db.properties.find( {"where": {"name": "temp_property"}}, transaction=transaction )[0] - - client.properties.delete( + + db.properties.delete( property_id=property_to_delete['id'], transaction=transaction ) @@ -1083,7 +1061,7 @@ When working with the PropertiesAPI, be prepared to handle potential errors: ```python try: # Attempt to find or delete a property - property_details = client.properties.find_by_id("non_existent_prop") + property_details = db.properties.find_by_id("non_existent_prop") except RushDBError as e: print(f"Error: {e}") print(f"Error Details: {e.details}") diff --git a/pyproject.toml b/pyproject.toml index 9697eff..eec6fe6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rushdb" -version = "0.3.0" +version = "1.0.0" description = "RushDB Python SDK" authors = ["RushDB Team "] license = "Apache-2.0" diff --git a/src/rushdb/api/labels.py b/src/rushdb/api/labels.py index 4710a61..cf3cd31 100644 --- a/src/rushdb/api/labels.py +++ b/src/rushdb/api/labels.py @@ -19,7 +19,7 @@ def list( return self.client._make_request( "POST", - "/api/v1/labels", + "/api/v1/labels/search", data=typing.cast(typing.Dict[str, typing.Any], query or {}), headers=headers, ) diff --git a/src/rushdb/api/properties.py b/src/rushdb/api/properties.py index 0f276cb..70dd99e 100644 --- a/src/rushdb/api/properties.py +++ b/src/rushdb/api/properties.py @@ -20,7 +20,7 @@ def find( return self.client._make_request( "POST", - "/api/v1/properties", + "/api/v1/properties/search", typing.cast(typing.Dict[str, typing.Any], query or {}), headers, ) diff --git a/src/rushdb/api/records.py b/src/rushdb/api/records.py index 5ba5c4d..528b908 100644 --- a/src/rushdb/api/records.py +++ b/src/rushdb/api/records.py @@ -59,7 +59,7 @@ def create( payload = { "label": label, - "payload": data, + "data": data, "options": options or {"returnResult": True, "suggestTypes": True}, } response = self.client._make_request( @@ -89,7 +89,7 @@ def create_many( payload = { "label": label, - "payload": data, + "data": data, "options": options or {"returnResult": True, "suggestTypes": True}, } response = self.client._make_request( @@ -120,7 +120,7 @@ def attach( if options: payload.update(typing.cast(typing.Dict[str, typing.Any], options)) return self.client._make_request( - "POST", f"/api/v1/records/{source_id}/relations", payload, headers + "POST", f"/api/v1/relationships/{source_id}", payload, headers ) def detach( @@ -146,7 +146,7 @@ def detach( if options: payload.update(typing.cast(typing.Dict[str, typing.Any], options)) return self.client._make_request( - "PUT", f"/api/v1/records/{source_id}/relations", payload, headers + "PUT", f"/api/v1/relationships/{source_id}", payload, headers ) def delete( @@ -219,7 +219,7 @@ def import_csv( payload = { "label": label, - "payload": csv_data, + "data": csv_data, "options": options or {"returnResult": True, "suggestTypes": True}, } diff --git a/src/rushdb/api/relationships.py b/src/rushdb/api/relationships.py index 712a9cc..4b2328c 100644 --- a/src/rushdb/api/relationships.py +++ b/src/rushdb/api/relationships.py @@ -44,7 +44,7 @@ async def find( # Construct path with query string query_string = f"?{urlencode(query_params)}" if query_params else "" - path = f"/records/relations/search{query_string}" + path = f"/relationships/search{query_string}" # Build headers with transaction if present headers = Transaction._build_transaction_header(transaction)