From 53b5b52bd0d225145cc094f6365220b2fa65506f Mon Sep 17 00:00:00 2001 From: Artemiy Vereshchinskiy Date: Tue, 10 Jun 2025 00:33:56 +0700 Subject: [PATCH 1/5] Update docs and labels api --- pyproject.toml | 2 +- src/rushdb/api/labels.py | 68 +++++- src/rushdb/api/properties.py | 159 ++++++++++++- src/rushdb/api/records.py | 398 ++++++++++++++++++++++++++++++-- src/rushdb/api/relationships.py | 100 +++++++- src/rushdb/api/transactions.py | 113 ++++++++- src/rushdb/client.py | 166 +++++++++++-- 7 files changed, 943 insertions(+), 63 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 135d6f3..11da9a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rushdb" -version = "1.2.0" +version = "1.4.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 417d0a1..365fb63 100644 --- a/src/rushdb/api/labels.py +++ b/src/rushdb/api/labels.py @@ -7,14 +7,76 @@ class LabelsAPI(BaseAPI): - """API for managing labels in RushDB.""" + """API client for managing labels in RushDB. - def list( + The LabelsAPI provides functionality for discovering and working with record labels + in the database. Labels are used to categorize and type records, similar to table + names in relational databases or document types in NoSQL databases. + + This class handles: + - Label discovery and listing + - Label searching and filtering + - Transaction support for operations + + Labels help organize and categorize records, making it easier to query and + understand the structure of data within the database. + + Attributes: + client: The underlying RushDB client instance for making HTTP requests. + + Example: + >>> from rushdb import RushDB + >>> client = RushDB(api_key="your_api_key") + >>> labels_api = client.labels + >>> + >>> # Get all labels in the database + >>> all_labels = labels_api.find() + >>> print("Available labels:", all_labels) + >>> + >>> # Search for specific labels + >>> from rushdb.models.search_query import SearchQuery + >>> query = SearchQuery(where={"name":{"$contains": "alice"}}) + >>> user_labels = labels_api.find(query) + """ + + def find( self, search_query: Optional[SearchQuery] = None, transaction: Optional[Transaction] = None, ) -> List[str]: - """List all labels.""" + """Search for and retrieve labels matching the specified criteria. + + Discovers labels (record types) that exist in the database and can optionally + filter them based on search criteria. Labels represent the different types + or categories of records stored in the database. + + Args: + search_query (Optional[SearchQuery], optional): The search criteria to filter labels. + If None, returns all labels in the database. Can include filters for + label names, patterns, or other metadata. Defaults to None. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + List[str]: List of label names (strings) matching the search criteria. + Each string represents a unique label/type used in the database. + + Raises: + RequestError: If the server request fails. + + Example: + >>> from rushdb.models.search_query import SearchQuery + >>> labels_api = LabelsAPI(client) + >>> + >>> # Get all labels + >>> all_labels = labels_api.find() + >>> print("Database contains these record types:", all_labels) + >>> + >>> # Search for labels amongst records matching a pattern + >>> query = SearchQuery(where={"name":{"$contains": "alice"}}) + >>> user_labels = labels_api.find(query) + >>> print("User-related labels:", user_labels) + """ headers = Transaction._build_transaction_header(transaction) return self.client._make_request( diff --git a/src/rushdb/api/properties.py b/src/rushdb/api/properties.py index d986c47..7dd9464 100644 --- a/src/rushdb/api/properties.py +++ b/src/rushdb/api/properties.py @@ -8,20 +8,84 @@ class PropertyValuesQuery(SearchQuery, total=False): - """Extended SearchQuery for property values endpoint with text search support.""" + """Extended SearchQuery for property values endpoint with text search support. + + This class extends the base SearchQuery to include additional search capabilities + specifically for querying property values, including text-based search functionality. + + Attributes: + query (Optional[str]): Text search string to filter property values. + When provided, performs a text-based search among the property values. + """ query: Optional[str] # For text search among values class PropertiesAPI(BaseAPI): - """API for managing properties in RushDB.""" + """API client for managing properties in RushDB. + + The PropertiesAPI provides functionality for working with database properties, + including searching for properties, retrieving individual properties by ID, + deleting properties, and querying property values. Properties represent + metadata about the structure and characteristics of data in the database. + + This class handles: + - Property discovery and searching + - Individual property retrieval + - Property deletion + - Property values querying with text search capabilities + - Transaction support for all operations + + Attributes: + client: The underlying RushDB client instance for making HTTP requests. + + Example: + >>> from rushdb import RushDB + >>> client = RushDB(api_key="your_api_key") + >>> properties_api = client.properties + >>> + >>> # Find all properties + >>> properties = properties_api.find() + >>> + >>> # Get a specific property + >>> property_obj = properties_api.find_by_id("property_123") + """ def find( self, search_query: Optional[SearchQuery] = None, transaction: Optional[Transaction] = None, ) -> List[Property]: - """List all properties.""" + """Search for and retrieve properties matching the specified criteria. + + Searches the database for properties that match the provided search query. + Properties represent metadata about the database structure and can be + filtered using various search criteria. + + Args: + search_query (Optional[SearchQuery], optional): The search criteria to filter properties. + If None, returns all properties. Can include filters for property names, + types, or other metadata. Defaults to None. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + List[Property]: List of Property objects matching the search criteria. + + Raises: + RequestError: If the server request fails. + + Example: + >>> from rushdb.models.search_query import SearchQuery + >>> properties_api = PropertiesAPI(client) + >>> + >>> # Find all properties + >>> all_properties = properties_api.find() + >>> + >>> # Find properties with specific criteria + >>> query = SearchQuery(where={"age":{"$gte": 30}}) + >>> string_properties = properties_api.find(query) + """ headers = Transaction._build_transaction_header(transaction) return self.client._make_request( @@ -34,7 +98,29 @@ def find( def find_by_id( self, property_id: str, transaction: Optional[Transaction] = None ) -> Property: - """Get a property by ID.""" + """Retrieve a specific property by its unique identifier. + + Fetches a single property from the database using its unique ID. + This method is useful when you know the exact property you want to retrieve. + + Args: + property_id (str): The unique identifier of the property to retrieve. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + Property: The Property object with the specified ID. + + Raises: + ValueError: If the property_id is invalid or empty. + NotFoundError: If no property exists with the specified ID. + RequestError: If the server request fails. + + Example: + >>> properties_api = PropertiesAPI(client) + >>> property_obj = properties_api.find_by_id("prop_123") + >>> print(property_obj.name) + """ headers = Transaction._build_transaction_header(transaction) return self.client._make_request( @@ -44,7 +130,33 @@ def find_by_id( def delete( self, property_id: str, transaction: Optional[Transaction] = None ) -> None: - """Delete a property.""" + """Delete a property from the database. + + Permanently removes a property and all its associated metadata from the database. + This operation cannot be undone, so use with caution. + + Args: + property_id (str): The unique identifier of the property to delete. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + None: This method does not return a value. + + Raises: + ValueError: If the property_id is invalid or empty. + NotFoundError: If no property exists with the specified ID. + RequestError: If the server request fails. + + Warning: + This operation permanently deletes the property and cannot be undone. + Ensure you have proper backups and confirmation before deleting properties. + + Example: + >>> properties_api = PropertiesAPI(client) + >>> properties_api.delete("prop_123") + >>> # Property is now permanently deleted + """ headers = Transaction._build_transaction_header(transaction) return self.client._make_request( @@ -57,7 +169,42 @@ def values( search_query: Optional[PropertyValuesQuery] = None, transaction: Optional[Transaction] = None, ) -> PropertyValuesData: - """Get values data for a property.""" + """Retrieve and search values data for a specific property. + + Gets statistical and analytical data about the values stored in a particular property, + including value distributions, counts, and other metadata. Supports text-based + searching within the property values. + + Args: + property_id (str): The unique identifier of the property to analyze. + search_query (Optional[PropertyValuesQuery], optional): Extended search query + that supports text search among property values. Can include: + - Standard SearchQuery filters + - query (str): Text search string to filter values + Defaults to None. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + PropertyValuesData: Object containing statistical and analytical data about + the property's values, including distributions, counts, and value samples. + + Raises: + ValueError: If the property_id is invalid or empty. + NotFoundError: If no property exists with the specified ID. + RequestError: If the server request fails. + + Example: + >>> properties_api = PropertiesAPI(client) + >>> + >>> # Get all values data for a property + >>> values_data = properties_api.values("prop_123") + >>> print(f"Total values: {values_data.total}") + >>> + >>> # Search for specific values + >>> query = PropertyValuesQuery(query="search_text") + >>> filtered_values = properties_api.values("prop_123", query) + """ headers = Transaction._build_transaction_header(transaction) return self.client._make_request( diff --git a/src/rushdb/api/records.py b/src/rushdb/api/records.py index 1895ef9..81ec568 100644 --- a/src/rushdb/api/records.py +++ b/src/rushdb/api/records.py @@ -9,7 +9,41 @@ class RecordsAPI(BaseAPI): - """API for managing records in RushDB.""" + """API client for managing records in RushDB. + + The RecordsAPI provides a comprehensive interface for performing CRUD operations + on records within a RushDB database. It supports creating, reading, updating, + and deleting records, as well as managing relationships between records and + importing data from various formats. + + This class handles: + - Individual and batch record creation + - Record updates (full replacement and partial updates) + - Record deletion by ID or search criteria + - Record searching and querying + - Relationship management (attach/detach operations) + - Data import from CSV format + - Transaction support for all operations + + The API supports flexible input formats for record identification, accepting + record IDs, record dictionaries, or Record objects interchangeably in most methods. + + Attributes: + client: The underlying RushDB client instance for making HTTP requests. + + Example: + >>> from rushdb import RushDB + >>> client = RushDB(api_key="your_api_key") + >>> records_api = client.records + >>> + >>> # Create a new record + >>> user = records_api.create("User", {"name": "John", "email": "john@example.com"}) + >>> + >>> # Search for records + >>> from rushdb.models.search_query import SearchQuery + >>> query = SearchQuery(where={"name": "John"}) + >>> results, total = records_api.find(query) + """ def set( self, @@ -17,7 +51,24 @@ def set( data: Dict[str, Any], transaction: Optional[Transaction] = None, ) -> Dict[str, str]: - """Update a record by ID.""" + """Replace all data in a record with new data. + + This method performs a complete replacement of the record's data, + removing any existing fields that are not present in the new data. + + Args: + record_id (str): The unique identifier of the record to update. + data (Dict[str, Any]): The new data to replace the existing record data. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + Dict[str, str]: Response from the server containing operation status. + + Raises: + ValueError: If the record_id is invalid or empty. + RequestError: If the server request fails. + """ headers = Transaction._build_transaction_header(transaction) return self.client._make_request("PUT", f"/records/{record_id}", data, headers) @@ -27,7 +78,24 @@ def update( data: Dict[str, Any], transaction: Optional[Transaction] = None, ) -> Dict[str, str]: - """Update a record by ID.""" + """Partially update a record with new or modified fields. + + This method performs a partial update, merging the provided data + with existing record data without removing existing fields. + + Args: + record_id (str): The unique identifier of the record to update. + data (Dict[str, Any]): The data to merge with the existing record data. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + Dict[str, str]: Response from the server containing operation status. + + Raises: + ValueError: If the record_id is invalid or empty. + RequestError: If the server request fails. + """ headers = Transaction._build_transaction_header(transaction) return self.client._make_request( @@ -41,17 +109,37 @@ def create( options: Optional[Dict[str, bool]] = None, transaction: Optional[Transaction] = None, ) -> Record: - """Create a new record. + """Create a new record in the database. + + Creates a single record with the specified label and data. The record + will be assigned a unique identifier and can be optionally configured + with parsing and response options. Args: - label: Label for the record - data: Record data - options: Optional parsing and response options (returnResult, suggestTypes) - transaction: Optional transaction object + label (str): The label/type to assign to the new record. + data (Dict[str, Any]): The data to store in the record. + options (Optional[Dict[str, bool]], optional): Configuration options for the operation. + Available options: + - returnResult (bool): Whether to return the created record data. Defaults to True. + - suggestTypes (bool): Whether to automatically suggest data types. 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. Returns: - Record object - :param + Record: A Record object representing the newly created record. + + Raises: + ValueError: If the label is empty or data is invalid. + RequestError: If the server request fails. + + Example: + >>> records_api = RecordsAPI(client) + >>> new_record = records_api.create( + ... label="User", + ... data={"name": "John Doe", "email": "john@example.com"} + ... ) + >>> print(new_record.data["name"]) + John Doe """ headers = Transaction._build_transaction_header(transaction) @@ -70,16 +158,39 @@ def create_many( options: Optional[Dict[str, bool]] = None, transaction: Optional[Transaction] = None, ) -> List[Record]: - """Create multiple records. + """Create multiple records in a single operation. + + Creates multiple records with the same label but different data. + This is more efficient than creating records individually when + you need to insert many records at once. Args: - label: Label for all records - data: List or Dict of record data - options: Optional parsing and response options (returnResult, suggestTypes) - transaction: Optional transaction object + label (str): The label/type to assign to all new records. + data (Union[Dict[str, Any], List[Dict[str, Any]]]): The data for the records. + Can be a single dictionary or a list of dictionaries. + options (Optional[Dict[str, bool]], optional): Configuration options for the operation. + Available options: + - returnResult (bool): Whether to return the created records data. Defaults to True. + - suggestTypes (bool): Whether to automatically suggest data types. 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. Returns: - List of Record objects + List[Record]: A list of Record objects representing the newly created records. + + Raises: + ValueError: If the label is empty or data is invalid. + RequestError: If the server request fails. + + Example: + >>> records_api = RecordsAPI(client) + >>> users_data = [ + ... {"name": "John Doe", "email": "john@example.com"}, + ... {"name": "Jane Smith", "email": "jane@example.com"} + ... ] + >>> new_records = records_api.create_many("User", users_data) + >>> print(len(new_records)) + 2 """ headers = Transaction._build_transaction_header(transaction) @@ -107,7 +218,43 @@ def attach( options: Optional[RelationshipOptions] = None, transaction: Optional[Transaction] = None, ) -> Dict[str, str]: - """Attach records to a source record.""" + """Create relationships by attaching target records to a source record. + + Establishes relationships between a source record and one or more target records. + The source and target can be specified using various formats including IDs, + record dictionaries, or Record objects. + + Args: + source (Union[str, Dict[str, Any]]): The source record to attach targets to. + Can be a record ID string or a record dictionary containing '__id'. + target (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): + The target record(s) to attach to the source. Accepts multiple formats: + - Single record ID (str) + - List of record IDs (List[str]) + - Record dictionary with '__id' field + - List of record dictionaries + - Record object + - List of Record objects + options (Optional[RelationshipOptions], optional): Additional options for the relationship. + Defaults to None. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + Dict[str, str]: Response from the server containing operation status. + + Raises: + ValueError: If source or target format is invalid or missing required '__id' fields. + RequestError: If the server request fails. + + Example: + >>> records_api = RecordsAPI(client) + >>> # Attach using record IDs + >>> response = records_api.attach("source_id", ["target1_id", "target2_id"]) + >>> + >>> # Attach using Record objects + >>> response = records_api.attach(source_record, target_records) + """ headers = Transaction._build_transaction_header(transaction) source_id = self._extract_target_ids(source)[0] @@ -133,7 +280,43 @@ def detach( options: Optional[RelationshipDetachOptions] = None, transaction: Optional[Transaction] = None, ) -> Dict[str, str]: - """Detach records from a source record.""" + """Remove relationships by detaching target records from a source record. + + Removes existing relationships between a source record and one or more target records. + The source and target can be specified using various formats including IDs, + record dictionaries, or Record objects. + + Args: + source (Union[str, Dict[str, Any]]): The source record to detach targets from. + Can be a record ID string or a record dictionary containing '__id'. + target (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): + The target record(s) to detach from the source. Accepts multiple formats: + - Single record ID (str) + - List of record IDs (List[str]) + - Record dictionary with '__id' field + - List of record dictionaries + - Record object + - List of Record objects + options (Optional[RelationshipDetachOptions], optional): Additional options for the detach operation. + Defaults to None. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + Dict[str, str]: Response from the server containing operation status. + + Raises: + ValueError: If source or target format is invalid or missing required '__id' fields. + RequestError: If the server request fails. + + Example: + >>> records_api = RecordsAPI(client) + >>> # Detach using record IDs + >>> response = records_api.detach("source_id", ["target1_id", "target2_id"]) + >>> + >>> # Detach using Record objects + >>> response = records_api.detach(source_record, target_records) + """ headers = Transaction._build_transaction_header(transaction) source_id = self._extract_target_ids(source)[0] @@ -148,7 +331,37 @@ def detach( def delete( self, search_query: SearchQuery, transaction: Optional[Transaction] = None ) -> Dict[str, str]: - """Delete records matching the query.""" + """Delete multiple records matching the specified search criteria. + + Deletes all records that match the provided search query. This operation + can delete multiple records in a single request based on the query conditions. + + Args: + search_query (SearchQuery): The search criteria to identify records for deletion. + This defines which records should be deleted based on their properties, + labels, relationships, or other query conditions. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + Dict[str, str]: Response from the server containing operation status and + information about the number of records deleted. + + Raises: + ValueError: If the search_query is invalid or malformed. + RequestError: If the server request fails. + + Warning: + This operation can delete multiple records at once. Use with caution + and ensure your search query is properly constructed to avoid + unintended deletions. + + Example: + >>> from rushdb.models.search_query import SearchQuery + >>> records_api = RecordsAPI(client) + >>> query = SearchQuery(where={"status": "inactive"}) + >>> response = records_api.delete(query) + """ headers = Transaction._build_transaction_header(transaction) return self.client._make_request( @@ -163,7 +376,39 @@ def delete_by_id( id_or_ids: Union[str, List[str]], transaction: Optional[Transaction] = None, ) -> Dict[str, str]: - """Delete records by ID(s).""" + """Delete one or more records by their unique identifiers. + + Deletes records by their specific IDs. Can handle both single record + deletion and bulk deletion of multiple records. + + Args: + id_or_ids (Union[str, List[str]]): The record identifier(s) to delete. + Can be a single record ID string or a list of record ID strings. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + Dict[str, str]: Response from the server containing operation status and + information about the deletion operation. + + Raises: + ValueError: If any of the provided IDs are invalid or empty. + RequestError: If the server request fails. + + Note: + When deleting multiple records (list of IDs), the operation uses a + batch delete with a limit of 1000 records. For single record deletion, + it uses a direct DELETE request. + + Example: + >>> records_api = RecordsAPI(client) + >>> # Delete a single record + >>> response = records_api.delete_by_id("record_123") + >>> + >>> # Delete multiple records + >>> record_ids = ["record_123", "record_456", "record_789"] + >>> response = records_api.delete_by_id(record_ids) + """ headers = Transaction._build_transaction_header(transaction) if isinstance(id_or_ids, list): @@ -182,8 +427,42 @@ def find( search_query: Optional[SearchQuery] = None, record_id: Optional[str] = None, transaction: Optional[Transaction] = None, - ) -> List[Record]: - """Find records matching the query.""" + ) -> (List[Record], int): + """Search for and retrieve records matching the specified criteria. + + Searches the database for records that match the provided search query. + Can perform both general searches across all records or searches within + the context of a specific record's relationships. + + Args: + search_query (Optional[SearchQuery], optional): The search criteria to filter records. + If None, returns all records (subject to default limits). Defaults to None. + record_id (Optional[str], optional): If provided, searches within the context + of this specific record's relationships. Defaults to None. + transaction (Optional[Transaction], optional): Transaction context for the operation. + If provided, the operation will be part of the transaction. Defaults to None. + + Returns: + Tuple[List[Record], int]: A tuple containing: + - List[Record]: List of Record objects matching the search criteria + - int: Total count of matching records (may be larger than returned list if pagination applies) + + Note: + The method includes exception handling that returns an empty list if an error occurs. + In production code, you may want to handle specific exceptions differently. + + Example: + >>> from rushdb.models.search_query import SearchQuery + >>> records_api = RecordsAPI(client) + >>> + >>> # Find all records with a specific label + >>> query = SearchQuery(labels=["User"]) + >>> records, total = records_api.find(query) + >>> print(f"Found {len(records)} records out of {total} total") + >>> + >>> # Find records related to a specific record + >>> related_records, total = records_api.find(query, record_id="parent_123") + """ try: headers = Transaction._build_transaction_header(transaction) @@ -195,7 +474,7 @@ def find( data=typing.cast(typing.Dict[str, typing.Any], search_query or {}), headers=headers, ) - return [Record(self.client, record) for record in response.get("data")] + return [Record(self.client, record) for record in response.get("data")], response.get('total') except Exception: return [] @@ -206,7 +485,40 @@ def import_csv( options: Optional[Dict[str, bool]] = None, transaction: Optional[Transaction] = None, ) -> List[Dict[str, Any]]: - """Import data from CSV.""" + """Import records from CSV data. + + Parses CSV data and creates multiple records from the content. Each row + in the CSV becomes a separate record with the specified label. The first + row is typically treated as headers defining the field names. + + Args: + label (str): The label/type to assign to all records created from the CSV. + csv_data (Union[str, bytes]): The CSV content to import. Can be provided + as a string or bytes object. + 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. + + Returns: + List[Dict[str, Any]]: List of dictionaries representing the imported records, + or server response data depending on the options. + + Raises: + ValueError: If the label is empty or CSV data is invalid/malformed. + RequestError: If the server request fails. + + Example: + >>> records_api = RecordsAPI(client) + >>> csv_content = '''name,email,age + ... John Doe,john@example.com,30 + ... Jane Smith,jane@example.com,25''' + >>> + >>> imported_records = records_api.import_csv("User", csv_content) + >>> print(f"Imported {len(imported_records)} records") + """ headers = Transaction._build_transaction_header(transaction) payload = { @@ -230,7 +542,43 @@ def _extract_target_ids( List["Record"], ] ) -> List[str]: - """Extract target IDs from various input types.""" + """Extract record IDs from various input types and formats. + + This utility method handles the conversion of different target input formats + into a standardized list of record ID strings. It supports multiple input + types commonly used throughout the API for specifying target records. + + Args: + target (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): + The target input to extract IDs from. Supported formats: + - str: Single record ID + - List[str]: List of record IDs + - Dict[str, Any]: Record dictionary containing '__id' field + - List[Dict[str, Any]]: List of record dictionaries with '__id' fields + - Record: Record object with data containing '__id' + - List[Record]: List of Record objects + + Returns: + List[str]: List of extracted record ID strings. + + Raises: + ValueError: If the target format is not supported or if required '__id' + fields are missing from dictionary or Record objects. + + Note: + This is an internal utility method used by attach() and detach() methods + to normalize their input parameters. + + Example: + >>> # Extract from string + >>> ids = RecordsAPI._extract_target_ids("record_123") + >>> # Returns: ["record_123"] + >>> + >>> # Extract from Record objects + >>> record_obj = Record(client, {"__id": "record_456", "name": "Test"}) + >>> ids = RecordsAPI._extract_target_ids(record_obj) + >>> # Returns: ["record_456"] + """ if isinstance(target, str): return [target] elif isinstance(target, list): diff --git a/src/rushdb/api/relationships.py b/src/rushdb/api/relationships.py index 0ec9f83..ab8494a 100644 --- a/src/rushdb/api/relationships.py +++ b/src/rushdb/api/relationships.py @@ -9,14 +9,56 @@ class PaginationParams(TypedDict, total=False): - """TypedDict for pagination parameters.""" + """TypedDict for pagination parameters in relationship queries. + + Defines the structure for pagination options when querying relationships, + allowing for efficient retrieval of large result sets. + + Attributes: + limit (int): Maximum number of relationships to return in a single request. + skip (int): Number of relationships to skip from the beginning of the result set. + Used for implementing pagination by skipping already retrieved items. + """ limit: int skip: int class RelationsAPI(BaseAPI): - """API for managing relationships in RushDB.""" + """API client for managing relationships in RushDB. + + The RelationsAPI provides functionality for querying and analyzing relationships + between records in the database. Relationships represent connections or associations + between different records, enabling graph-like data structures and complex queries. + + This class handles: + - Relationship discovery and searching + - Pagination support for large result sets + - Transaction support for operations + - Flexible querying with various search criteria + + Relationships are the connections between records that enable building complex + data models and performing graph traversals within the database. + + Attributes: + client: The underlying RushDB client instance for making HTTP requests. + + Note: + This API currently contains async methods. Ensure you're using it in an + async context or consider updating to sync methods if needed. + + Example: + >>> from rushdb import RushDB + >>> client = RushDB(api_key="your_api_key") + >>> relations_api = client.relationships + >>> + >>> # Find all relationships + >>> relationships = await relations_api.find() + >>> + >>> # Find relationships with pagination + >>> pagination = {"limit": 50, "skip": 0} + >>> page_1 = await relations_api.find(pagination=pagination) + """ async def find( self, @@ -24,18 +66,56 @@ async def find( pagination: Optional[PaginationParams] = None, transaction: Optional[Union[Transaction, str]] = None, ) -> List[Relationship]: - """Find relations matching the search parameters. + """Search for and retrieve relationships matching the specified criteria. + + Asynchronously searches the database for relationships that match the provided + search query. Supports pagination for efficient handling of large result sets + and can operate within transaction contexts. Args: - query: Search query parameters - pagination: Optional pagination parameters (limit and skip) - transaction: Optional transaction context or transaction ID + search_query (Optional[SearchQuery], optional): The search criteria to filter relationships. + If None, returns all relationships (subject to pagination limits). Can include + filters for relationship types, source/target records, or other metadata. + Defaults to None. + pagination (Optional[PaginationParams], optional): Pagination options to control + result set size and offset. Contains: + - limit (int): Maximum number of relationships to return + - skip (int): Number of relationships to skip from the beginning + Defaults to None. + transaction (Optional[Union[Transaction, str]], optional): Transaction context + for the operation. Can be either a Transaction object or a transaction ID string. + If provided, the operation will be part of the transaction. Defaults to None. Returns: - List of matching relations - :param transaction: - :param pagination: - :param search_query: + List[Relationship]: List of Relationship objects matching the search criteria. + The list will be limited by pagination parameters if provided. + + Raises: + RequestError: If the server request fails. + + Note: + This is an async method and must be awaited when called. + + Example: + >>> from rushdb.models.search_query import SearchQuery + >>> relations_api = RelationsAPI(client) + >>> + >>> # Find all relationships + >>> all_relationships = await relations_api.find() + >>> + >>> # Find relationships with pagination + >>> pagination = PaginationParams(limit=50, skip=0) + >>> first_page = await relations_api.find(pagination=pagination) + >>> + >>> # Find relationships with search criteria + >>> query = SearchQuery(where={"flight_type": "domestic"}) + >>> follow_relationships = await relations_api.find(search_query=query) + >>> + >>> # Combine search and pagination + >>> filtered_page = await relations_api.find( + ... search_query=query, + ... pagination=PaginationParams(limit=25, skip=25) + ... ) """ # Build query string for pagination query_params = {} diff --git a/src/rushdb/api/transactions.py b/src/rushdb/api/transactions.py index 666a05e..3cd8ca3 100644 --- a/src/rushdb/api/transactions.py +++ b/src/rushdb/api/transactions.py @@ -5,21 +5,124 @@ class TransactionsAPI(BaseAPI): - """API for managing transactions in RushDB.""" + """API client for managing database transactions in RushDB. + + The TransactionsAPI provides functionality for creating and managing database + transactions, which allow multiple operations to be grouped together and + executed atomically. Transactions ensure data consistency and provide + rollback capabilities if operations fail. + + This class handles: + - Transaction creation and initialization + - Transaction commitment (making changes permanent) + - Transaction rollback (undoing changes) + - Transaction lifecycle management + + Transactions are essential for maintaining data integrity when performing + multiple related operations that should succeed or fail together. + + Attributes: + client: The underlying RushDB client instance for making HTTP requests. + + Example: + >>> from rushdb import RushDB + >>> client = RushDB(api_key="your_api_key") + >>> tx_api = client.transactions + >>> + >>> # Begin a new transaction + >>> transaction = tx_api.begin(ttl=10000) # 10 second TTL + >>> + >>> # Use transaction with other API calls + >>> try: + ... client.records.create("User", {"name": "John"}, transaction=transaction) + ... client.records.create("User", {"name": "Jane"}, transaction=transaction) + ... transaction.commit() # Makes changes permanent + >>> except Exception: + ... transaction.rollback() # Undoes all changes + """ def begin(self, ttl: Optional[int] = None) -> Transaction: - """Begin a new transaction. + """Begin a new database transaction. + + Creates a new transaction that can be used to group multiple database + operations together. The transaction will have a time-to-live (TTL) + after which it will automatically expire if not committed or rolled back. + + Args: + ttl (Optional[int], optional): Time-to-live in milliseconds for the transaction. + After this time, the transaction will automatically expire and be rolled back. + If None, defaults to 5000ms (5 seconds). Defaults to None. Returns: - Transaction object + Transaction: A Transaction object that can be used with other API operations. + The transaction provides commit() and rollback() methods for controlling + the transaction lifecycle. + + Raises: + RequestError: If the server request fails or transaction creation is denied. + + Example: + >>> tx_api = TransactionsAPI(client) + >>> + >>> # Begin transaction with default TTL (5 seconds) + >>> transaction = tx_api.begin() + >>> + >>> # Begin transaction with custom TTL (30 seconds) + >>> long_transaction = tx_api.begin(ttl=30000) + >>> + >>> # Use the transaction with other operations + >>> try: + ... client.records.create("User", {"name": "Alice"}, transaction=transaction) + ... transaction.commit() + >>> except Exception: + ... transaction.rollback() """ response = self.client._make_request("POST", "/tx", {"ttl": ttl or 5000}) return Transaction(self.client, response.get("data")["id"]) def _commit(self, transaction_id: str) -> None: - """Internal method to commit a transaction.""" + """Internal method to commit a transaction. + + Commits the specified transaction, making all operations performed within + the transaction permanent. This is an internal method that should not be + called directly - use the commit() method on the Transaction object instead. + + Args: + transaction_id (str): The unique identifier of the transaction to commit. + + Returns: + None: This method does not return a value. + + Raises: + ValueError: If the transaction_id is invalid or empty. + NotFoundError: If no transaction exists with the specified ID. + RequestError: If the server request fails or the transaction cannot be committed. + + Note: + This is an internal method. Use transaction.commit() instead of calling this directly. + """ return self.client._make_request("POST", f"/tx/{transaction_id}/commit", {}) def _rollback(self, transaction_id: str) -> None: - """Internal method to rollback a transaction.""" + """Internal method to rollback a transaction. + + Rolls back the specified transaction, undoing all operations performed within + the transaction and restoring the database to its state before the transaction + began. This is an internal method that should not be called directly - use the + rollback() method on the Transaction object instead. + + Args: + transaction_id (str): The unique identifier of the transaction to rollback. + + Returns: + None: This method does not return a value. + + Raises: + ValueError: If the transaction_id is invalid or empty. + NotFoundError: If no transaction exists with the specified ID. + RequestError: If the server request fails or the transaction cannot be rolled back. + + Note: + This is an internal method. Use transaction.rollback() instead of calling this directly. + """ return self.client._make_request("POST", f"/tx/{transaction_id}/rollback", {}) diff --git a/src/rushdb/client.py b/src/rushdb/client.py index 39a13c4..9e015d8 100644 --- a/src/rushdb/client.py +++ b/src/rushdb/client.py @@ -1,4 +1,9 @@ -"""RushDB Client""" +"""RushDB Python Client + +This module provides the main client interface for interacting with RushDB, +a modern graph database. The client handles authentication, request management, +and provides access to all RushDB API endpoints through specialized API classes. +""" import json import urllib.error @@ -14,16 +19,88 @@ class RushDB: - """Main client for interacting with RushDB.""" + """Main client for interacting with RushDB graph database. + + The RushDB client is the primary interface for connecting to and interacting + with a RushDB instance. It provides access to all database operations through + specialized API endpoints for records, properties, labels, relationships, and + transactions. + + The client handles: + - Authentication via API keys + - HTTP request management and error handling + - Connection pooling and URL management + - Access to all RushDB API endpoints + - JSON serialization/deserialization + - Error handling and custom exceptions + + Attributes: + DEFAULT_BASE_URL (str): Default RushDB API endpoint URL + base_url (str): The configured base URL for API requests + api_key (str): The API key used for authentication + records (RecordsAPI): API interface for record operations + properties (PropertiesAPI): API interface for property operations + labels (LabelsAPI): API interface for label operations + transactions (TransactionsAPI): API interface for transaction operations + + Example: + >>> from rushdb import RushDB + >>> + >>> # Initialize client with API key + >>> client = RushDB(api_key="your_api_key_here") + >>> + >>> # Use with custom server URL + >>> client = RushDB( + ... api_key="your_api_key_here", + ... base_url="https://your-rushdb-instance.com/api/v1" + ... ) + >>> + >>> # Check connection + >>> if client.ping(): + ... print("Connected to RushDB successfully!") + >>> + >>> # Create a record + >>> user = client.records.create("User", {"name": "John", "email": "john@example.com"}) + >>> + >>> # Start a transaction + >>> transaction = client.transactions.begin() + >>> try: + ... client.records.create("User", {"name": "Alice"}, transaction=transaction) + ... client.records.create("User", {"name": "Bob"}, transaction=transaction) + ... transaction.commit() + >>> except Exception: + ... transaction.rollback() + """ DEFAULT_BASE_URL = "https://api.rushdb.com/api/v1" def __init__(self, api_key: str, base_url: Optional[str] = None): - """Initialize the RushDB client. + """Initialize the RushDB client with authentication and connection settings. + + Sets up the client with the necessary authentication credentials and server + configuration. Initializes all API endpoint interfaces for database operations. Args: - api_key: The API key for authentication - base_url: Optional base URL for the RushDB server (default: https://api.rushdb.com/api/v1) + api_key (str): The API key for authenticating with the RushDB server. + This key should have appropriate permissions for the operations you + plan to perform. + base_url (Optional[str], optional): Custom base URL for the RushDB server. + If not provided, uses the default public RushDB API endpoint. + Should include the protocol (https://) and path to the API version. + Defaults to None, which uses DEFAULT_BASE_URL. + + Raises: + ValueError: If api_key is empty or None. + + Example: + >>> # Using default public API + >>> client = RushDB(api_key="your_api_key") + >>> + >>> # Using custom server + >>> client = RushDB( + ... api_key="your_api_key", + ... base_url="https://my-rushdb.company.com/api/v1" + ... ) """ self.base_url = (base_url or self.DEFAULT_BASE_URL).rstrip("/") self.api_key = api_key @@ -40,17 +117,47 @@ def _make_request( headers: Optional[Dict[str, str]] = None, params: Optional[Dict[str, Any]] = None, ) -> Any: - """Make an HTTP request to the RushDB server. + """Make an authenticated HTTP request to the RushDB server. + + This is the core method that handles all HTTP communication with the RushDB + server. It manages URL construction, authentication headers, request encoding, + response parsing, and error handling. Args: - method: HTTP method (GET, POST, PUT, DELETE) - path: API endpoint path - data: Request body data - headers: Optional request headers - params: Optional URL query parameters + method (str): HTTP method to use for the request (GET, POST, PUT, DELETE, PATCH). + path (str): API endpoint path relative to the base URL. Can start with or + without a leading slash. + data (Optional[Dict], optional): Request body data to be JSON-encoded. + Will be serialized to JSON and sent as the request body. Defaults to None. + headers (Optional[Dict[str, str]], optional): Additional HTTP headers to include + in the request. These will be merged with default headers (authentication, + content-type). Defaults to None. + params (Optional[Dict[str, Any]], optional): URL query parameters to append + to the request URL. Will be properly URL-encoded. Defaults to None. Returns: - The parsed JSON response + Any: The parsed JSON response from the server. The exact structure depends + on the specific API endpoint called. + + Raises: + RushDBError: If the server returns an HTTP error status, connection fails, + or the response cannot be parsed as JSON. The exception includes details + about the specific error that occurred. + + Note: + This is an internal method used by the API classes. You typically won't + need to call this directly - use the methods on the API classes instead. + + Example: + >>> # This is typically called internally by API methods + >>> response = client._make_request("GET", "/records/search", data={"limit": 10}) + >>> + >>> # With query parameters + >>> response = client._make_request( + ... "GET", + ... "/properties", + ... params={"limit": 50, "skip": 0} + ... ) """ # Ensure path starts with / if not path.startswith("/"): @@ -98,7 +205,40 @@ def _make_request( raise RushDBError(f"Invalid JSON response: {str(e)}") def ping(self) -> bool: - """Check if the server is reachable.""" + """Test connectivity to the RushDB server. + + Performs a simple health check request to verify that the RushDB server + is reachable and responding. This is useful for testing connections, + validating configuration, or implementing health monitoring. + + Returns: + bool: True if the server is reachable and responds successfully, + False if there are any connection issues, authentication problems, + or server errors. + + Note: + This method catches all RushDBError exceptions and returns False, + making it safe for use in conditional checks without needing + explicit exception handling. + + Example: + >>> client = RushDB(api_key="your_api_key") + >>> + >>> # Check if server is available + >>> if client.ping(): + ... print("RushDB server is online and accessible") + ... # Proceed with database operations + ... else: + ... print("Cannot connect to RushDB server") + ... # Handle connection failure + >>> + >>> # Use in application startup + >>> def initialize_database(): + ... client = RushDB(api_key=os.getenv("RUSHDB_API_KEY")) + ... if not client.ping(): + ... raise RuntimeError("Database connection failed") + ... return client + """ try: self._make_request("GET", "/") return True From 79ff3bfd7ccde62e953c4b585edde0aa4473ea33 Mon Sep 17 00:00:00 2001 From: Artemiy Vereshchinskiy Date: Tue, 10 Jun 2025 01:10:12 +0700 Subject: [PATCH 2/5] Formatting --- src/rushdb/api/records.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rushdb/api/records.py b/src/rushdb/api/records.py index 81ec568..a5a18bc 100644 --- a/src/rushdb/api/records.py +++ b/src/rushdb/api/records.py @@ -474,9 +474,11 @@ def find( data=typing.cast(typing.Dict[str, typing.Any], search_query or {}), headers=headers, ) - return [Record(self.client, record) for record in response.get("data")], response.get('total') + return [ + Record(self.client, record) for record in response.get("data") + ], response.get("total") or 0 except Exception: - return [] + return [], 0 def import_csv( self, From 4bf408b8150c3c1ecf2f4d127938fb16e31eff3b Mon Sep 17 00:00:00 2001 From: Artemiy Vereshchinskiy Date: Tue, 10 Jun 2025 01:15:45 +0700 Subject: [PATCH 3/5] Explicit type --- src/rushdb/api/records.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rushdb/api/records.py b/src/rushdb/api/records.py index a5a18bc..94cc4c6 100644 --- a/src/rushdb/api/records.py +++ b/src/rushdb/api/records.py @@ -1,5 +1,5 @@ import typing -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, Tuple from ..models.record import Record from ..models.relationship import RelationshipDetachOptions, RelationshipOptions @@ -427,7 +427,7 @@ def find( search_query: Optional[SearchQuery] = None, record_id: Optional[str] = None, transaction: Optional[Transaction] = None, - ) -> (List[Record], int): + ) -> Tuple[List[Record], int]: """Search for and retrieve records matching the specified criteria. Searches the database for records that match the provided search query. From 2c8e861e1ee76954028790c7de6e32b6b9cbf77e Mon Sep 17 00:00:00 2001 From: Artemiy Vereshchinskiy Date: Tue, 10 Jun 2025 01:17:53 +0700 Subject: [PATCH 4/5] Import order fix --- src/rushdb/api/records.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rushdb/api/records.py b/src/rushdb/api/records.py index 94cc4c6..6f0e7ea 100644 --- a/src/rushdb/api/records.py +++ b/src/rushdb/api/records.py @@ -1,5 +1,5 @@ import typing -from typing import Any, Dict, List, Optional, Union, Tuple +from typing import Any, Dict, List, Optional, Tuple, Union from ..models.record import Record from ..models.relationship import RelationshipDetachOptions, RelationshipOptions From a8231267fdc615c7de15513b83a4621593744979 Mon Sep 17 00:00:00 2001 From: Artemiy Vereshchinskiy Date: Tue, 10 Jun 2025 01:20:31 +0700 Subject: [PATCH 5/5] Add getter method to Record class --- src/rushdb/models/record.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rushdb/models/record.py b/src/rushdb/models/record.py index d12966f..ff1e7e1 100644 --- a/src/rushdb/models/record.py +++ b/src/rushdb/models/record.py @@ -108,3 +108,9 @@ def delete(self, transaction: Optional[Transaction] = None) -> Dict[str, str]: def __repr__(self) -> str: """String representation of record.""" return f"Record(id='{self.id}')" + + def __getitem__(self, key: str) -> Any: + return self.data[key] + + def get(self, key: str, default: Any = None) -> Any: + return self.data.get(key, default)