langgraph-store-dynamodb 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+
11
+ # Virtual environments
12
+ venv/
13
+ env/
14
+ .env/
15
+ .venv/
16
+ ENV/
17
+
18
+ # IDE specific files
19
+ .idea/
20
+ .vscode/
21
+ *.swp
22
+ *.swo
23
+
24
+ # Unit test / coverage reports
25
+ htmcov/
26
+ .tox/
27
+ .coverage
28
+ .coverage.*
29
+ .cache
30
+ nosetests.xml
31
+ coverage.xml
32
+ *.cover
33
+
34
+ # Jupyter Notebook
35
+ .ipynb_checkpoints
36
+
37
+ # Environment variables
38
+ .env
39
+
40
+ # Local development settings
41
+ *.log
42
+ .DS_Store
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: langgraph_store_dynamodb
3
+ Version: 0.1.0
4
+ Summary: DynamoDB dtore for LangGraph
5
+ Author-email: Kamal <skamalj@github.com>
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python :: 3
9
+ Requires-Python: >=3.9
10
+ Requires-Dist: boto3
11
+ Requires-Dist: botocore
12
+ Requires-Dist: langchain-core
13
+ Requires-Dist: langgraph
14
+ Provides-Extra: dev
15
+ Requires-Dist: black; extra == 'dev'
16
+ Requires-Dist: isort; extra == 'dev'
17
+ Requires-Dist: mypy; extra == 'dev'
18
+ Requires-Dist: pytest-cov; extra == 'dev'
19
+ Requires-Dist: pytest>=7.0; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # LangGraph DynamoDB Store
23
+
24
+ A DynamoDB-based store implementation for LangGraph that allows long term memory implementation
25
+
26
+ bash
27
+ pip install langgraph-store-dynamodb
28
+
29
+
30
+ ## Usage
31
+
32
+ ### Basic Initialization
33
+
34
+ python
35
+ from langgraph_store_dynamodb import DynamoDBStore
36
+
37
+ # Initialize the store with a table name
38
+ store = DynamoDBStore(
39
+ table_name="your-dynamodb-table-name",
40
+ max_read_request_units=10, # Optional, default is 10
41
+ max_write_request_units=10 # Optional, default is 10
42
+ )
43
+
44
+
45
+ ### Alternative Initialization Using Context Manager
46
+
47
+ python
48
+ from langgraph_dynamodb_checkpoint import DynamoDBStore
49
+
50
+ with DynamoDBStore.from_conn_info(table_name="your-dynamodb-table-name") as store:
51
+ # Use the store here
52
+ pass
53
+
54
+
55
+ ## Parameters
56
+
57
+ ### DynamoDBStore Constructor
58
+
59
+ - `table_name` (str): Name of the DynamoDB table to use for storing checkpoints
60
+ - `max_read_request_units` (int, optional): Maximum read request units for the DynamoDB table. Defaults to 10
61
+ - `max_write_request_units` (int, optional): Maximum write request units for the DynamoDB table. Defaults to 10
62
+
63
+ ## Table Structure
64
+
65
+ The store automatically creates a DynamoDB table if it doesn't exist, with the following structure:
66
+
67
+ - Partition Key (PK): String type, used for namespace
68
+ - Sort Key (SK): String type, used for memory key
69
+
70
+ ## AWS Configuration
71
+
72
+ Ensure you have proper AWS credentials configured either through:
73
+ - Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
74
+ - AWS credentials file (~/.aws/credentials)
75
+ - IAM role when running on AWS services
76
+
77
+ The AWS credentials should have permissions to:
78
+ - Create DynamoDB tables (if table doesn't exist)
79
+ - Read and write to DynamoDB tables
80
+
81
+ ## Notes
82
+
83
+ - The store automatically creates the DynamoDB table if it doesn't exist
84
+ - Uses on-demand billing mode for DynamoDB
85
+ - Implements methods required by the LangGraph BaseStore interface
@@ -0,0 +1,64 @@
1
+ # LangGraph DynamoDB Store
2
+
3
+ A DynamoDB-based store implementation for LangGraph that allows long term memory implementation
4
+
5
+ bash
6
+ pip install langgraph-store-dynamodb
7
+
8
+
9
+ ## Usage
10
+
11
+ ### Basic Initialization
12
+
13
+ python
14
+ from langgraph_store_dynamodb import DynamoDBStore
15
+
16
+ # Initialize the store with a table name
17
+ store = DynamoDBStore(
18
+ table_name="your-dynamodb-table-name",
19
+ max_read_request_units=10, # Optional, default is 10
20
+ max_write_request_units=10 # Optional, default is 10
21
+ )
22
+
23
+
24
+ ### Alternative Initialization Using Context Manager
25
+
26
+ python
27
+ from langgraph_dynamodb_checkpoint import DynamoDBStore
28
+
29
+ with DynamoDBStore.from_conn_info(table_name="your-dynamodb-table-name") as store:
30
+ # Use the store here
31
+ pass
32
+
33
+
34
+ ## Parameters
35
+
36
+ ### DynamoDBStore Constructor
37
+
38
+ - `table_name` (str): Name of the DynamoDB table to use for storing checkpoints
39
+ - `max_read_request_units` (int, optional): Maximum read request units for the DynamoDB table. Defaults to 10
40
+ - `max_write_request_units` (int, optional): Maximum write request units for the DynamoDB table. Defaults to 10
41
+
42
+ ## Table Structure
43
+
44
+ The store automatically creates a DynamoDB table if it doesn't exist, with the following structure:
45
+
46
+ - Partition Key (PK): String type, used for namespace
47
+ - Sort Key (SK): String type, used for memory key
48
+
49
+ ## AWS Configuration
50
+
51
+ Ensure you have proper AWS credentials configured either through:
52
+ - Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
53
+ - AWS credentials file (~/.aws/credentials)
54
+ - IAM role when running on AWS services
55
+
56
+ The AWS credentials should have permissions to:
57
+ - Create DynamoDB tables (if table doesn't exist)
58
+ - Read and write to DynamoDB tables
59
+
60
+ ## Notes
61
+
62
+ - The store automatically creates the DynamoDB table if it doesn't exist
63
+ - Uses on-demand billing mode for DynamoDB
64
+ - Implements methods required by the LangGraph BaseStore interface
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "langgraph_store_dynamodb"
7
+ version = "0.1.0"
8
+ description = "DynamoDB dtore for LangGraph"
9
+ authors = [{name = "Kamal", email = "skamalj@github.com"}]
10
+ readme = "README.md"
11
+ requires-python = ">=3.9"
12
+ dependencies = [
13
+ "langchain-core",
14
+ "langgraph",
15
+ "boto3",
16
+ "botocore"
17
+ ]
18
+ classifiers = [
19
+ "Programming Language :: Python :: 3",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=7.0",
27
+ "pytest-cov",
28
+ "black",
29
+ "isort",
30
+ "mypy"
31
+ ]
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/langgraph_store_dynamodb"]
35
+
36
+ [tool.black]
37
+ line-length = 88
38
+ target-version = ['py39']
39
+
40
+ [tool.isort]
41
+ profile = "black"
42
+ multi_line_output = 3
@@ -0,0 +1,64 @@
1
+ # LangGraph DynamoDB Store
2
+
3
+ A DynamoDB-based store implementation for LangGraph that allows long term memory implementation
4
+
5
+ bash
6
+ pip install langgraph-store-dynamodb
7
+
8
+
9
+ ## Usage
10
+
11
+ ### Basic Initialization
12
+
13
+ python
14
+ from langgraph_store_dynamodb import DynamoDBStore
15
+
16
+ # Initialize the store with a table name
17
+ store = DynamoDBStore(
18
+ table_name="your-dynamodb-table-name",
19
+ max_read_request_units=10, # Optional, default is 10
20
+ max_write_request_units=10 # Optional, default is 10
21
+ )
22
+
23
+
24
+ ### Alternative Initialization Using Context Manager
25
+
26
+ python
27
+ from langgraph_dynamodb_checkpoint import DynamoDBStore
28
+
29
+ with DynamoDBStore.from_conn_info(table_name="your-dynamodb-table-name") as store:
30
+ # Use the store here
31
+ pass
32
+
33
+
34
+ ## Parameters
35
+
36
+ ### DynamoDBStore Constructor
37
+
38
+ - `table_name` (str): Name of the DynamoDB table to use for storing checkpoints
39
+ - `max_read_request_units` (int, optional): Maximum read request units for the DynamoDB table. Defaults to 10
40
+ - `max_write_request_units` (int, optional): Maximum write request units for the DynamoDB table. Defaults to 10
41
+
42
+ ## Table Structure
43
+
44
+ The store automatically creates a DynamoDB table if it doesn't exist, with the following structure:
45
+
46
+ - Partition Key (PK): String type, used for namespace
47
+ - Sort Key (SK): String type, used for memory key
48
+
49
+ ## AWS Configuration
50
+
51
+ Ensure you have proper AWS credentials configured either through:
52
+ - Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
53
+ - AWS credentials file (~/.aws/credentials)
54
+ - IAM role when running on AWS services
55
+
56
+ The AWS credentials should have permissions to:
57
+ - Create DynamoDB tables (if table doesn't exist)
58
+ - Read and write to DynamoDB tables
59
+
60
+ ## Notes
61
+
62
+ - The store automatically creates the DynamoDB table if it doesn't exist
63
+ - Uses on-demand billing mode for DynamoDB
64
+ - Implements methods required by the LangGraph BaseStore interface
File without changes
@@ -0,0 +1,165 @@
1
+ import boto3
2
+ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
3
+ from boto3.dynamodb.conditions import Key
4
+ from botocore.exceptions import ClientError
5
+ from langgraph.store.base import BaseStore, Item, PutOp, Result, SearchItem, NamespacePath
6
+ from datetime import datetime
7
+
8
+
9
+ class DynamoDBStore(BaseStore):
10
+ def __init__(self, table_name: str, max_read_request_units: int = 10, max_write_request_units: int = 10):
11
+ super().__init__()
12
+ self.dynamodb = boto3.resource('dynamodb')
13
+ self.table = self._get_or_create_table(table_name, max_read_request_units,max_write_request_units)
14
+
15
+ def _get_or_create_table(self, table_name: str, max_read_request_units: int, max_write_request_units: int):
16
+ try:
17
+ # Attempt to load the table
18
+ table = self.dynamodb.Table(table_name)
19
+ table.load() # This will raise an exception if the table does not exist
20
+ print(f"Table '{table_name}' already exists.")
21
+ return table
22
+ except ClientError as e:
23
+ if e.response['Error']['Code'] == 'ResourceNotFoundException':
24
+ # Table does not exist, create it
25
+ print(f"Table '{table_name}' not found. Creating table...")
26
+ key_schema = [
27
+ {'AttributeName': 'PK', 'KeyType': 'HASH'}, # Partition key
28
+ {'AttributeName': 'SK', 'KeyType': 'RANGE'}, # Sort key
29
+ ]
30
+ attribute_definitions = [
31
+ {'AttributeName': 'PK', 'AttributeType': 'S'}, # String type
32
+ {"AttributeName": "SK", "AttributeType": 'S'},
33
+ ]
34
+
35
+ table = self.dynamodb.create_table(
36
+ TableName=table_name,
37
+ KeySchema=key_schema,
38
+ AttributeDefinitions=attribute_definitions,
39
+ BillingMode='PAY_PER_REQUEST',
40
+ OnDemandThroughput={
41
+ 'MaxReadRequestUnits': max_read_request_units,
42
+ 'MaxWriteRequestUnits': max_write_request_units
43
+ }
44
+ )
45
+ table.wait_until_exists() # Wait for the table to become active
46
+ print(f"Table '{table_name}' created successfully.")
47
+ return table
48
+ else:
49
+ raise # Re-raise any other exceptions
50
+ from datetime import datetime
51
+
52
+ def _map_to_item(result_dict, namespace, return_type='Item'):
53
+ # Extract values from the result_dict
54
+ key = result_dict['PK'] # Using 'PK' as the unique key
55
+ value = result_dict['value']
56
+ created_at = result_dict['created_at']
57
+ updated_at = result_dict['updated_at']
58
+
59
+ target_class = Item if return_type == 'Item' else SearchItem
60
+
61
+ # Create an Item object
62
+ item = target_class(
63
+ value=value,
64
+ key=key,
65
+ namespace=namespace,
66
+ created_at=created_at,
67
+ updated_at=updated_at
68
+ )
69
+ return item
70
+
71
+ def batch(self, ops: Iterable[PutOp]) -> list[Result]:
72
+ results = []
73
+ for op in ops:
74
+ composite_key = self._construct_composite_key(op.namespace, op.key)
75
+ if op.value is None:
76
+ # Delete operation
77
+ result = self.delete(namespace=composite_key[0], key=composite_key[1])
78
+ else:
79
+ result = self.put(
80
+ namespace=composite_key[0], key=composite_key[1], value=op.value
81
+ )
82
+ # Append the operation result
83
+ results.append(result)
84
+ return results
85
+
86
+ def get(self, namespace: Tuple[str, ...], key: str) -> Optional[Item]:
87
+ """
88
+ Retrieve an item based on its composite key (namespace, key).
89
+ """
90
+ composite_key = self._construct_composite_key(namespace, key)
91
+ response = self.table.get_item(Key={'PK': composite_key[0], 'SK': composite_key[1]})
92
+ item = response.get('Item')
93
+ if item:
94
+ return self._map_to_item(item, namespace)
95
+ return None
96
+
97
+ def search(
98
+ self,
99
+ namespace_prefix: Tuple[str, ...],
100
+ *,
101
+ query: Optional[str] = None,
102
+ filter: Optional[Dict[str, Any]] = None,
103
+ limit: int = 10,
104
+ offset: int = 0
105
+ ) -> List[SearchItem]:
106
+ """
107
+ Search for items in a given namespace, applying optional query and filter.
108
+ """
109
+ namespace = ':'.join(namespace_prefix)
110
+ key_condition = "PK = :partitionkeyval AND SK = :sortkeyval"
111
+ filter_expression = None
112
+
113
+ response = self.table.query(
114
+ ExpressionAttributeValues={
115
+ ':PK': namespace
116
+ },
117
+ KeyConditionExpression='PK = :PK',
118
+ Limit=limit
119
+ )
120
+
121
+ items = response.get('Items', [])
122
+ return [self._map_to_item(item, namespace, 'SearchItem') for item in items]
123
+
124
+ def put(
125
+ self,
126
+ namespace: Tuple[str, ...],
127
+ key: str,
128
+ value: Dict[str, Any],
129
+ index: Optional[Union[Literal[False], List[str]]] = None
130
+ ) -> None:
131
+ """
132
+ Insert or update an item in the table.
133
+ """
134
+ composite_key = self._construct_composite_key(namespace, key)
135
+ existing_item = self.get(namespace=composite_key[0], key=composite_key[0])
136
+ current_time = datetime.utcnow()
137
+ item = {
138
+ 'PK': composite_key[0],
139
+ 'SK': composite_key[0],
140
+ 'value': value,
141
+ 'created_at': existing_item.created_at if existing_item else current_time,
142
+ 'updated_at': current_time,
143
+ }
144
+ self.table.put_item(Item=item)
145
+
146
+ def delete(self, namespace: Tuple[str, ...], key: str) -> None:
147
+ """
148
+ Delete an item based on its composite key (namespace, key).
149
+ """
150
+ composite_key = self._construct_composite_key(namespace, key)
151
+ self.table.delete_item(Key={'PK': composite_key[0], 'SK': composite_key[1]})
152
+
153
+ def list_namespaces(self, *, prefix: Optional[NamespacePath] = None,
154
+ suffix: Optional[NamespacePath] = None,
155
+ max_depth: Optional[int] = None,
156
+ limit: int = 100, offset: int = 0) -> list[tuple[str, ...]]:
157
+ raise NotImplementedError("The 'list_namespaces' method is not implemented yet.")
158
+
159
+
160
+ def _construct_composite_key(self, namespace: Tuple[str, ...], key: str) -> Tuple[str, str]:
161
+ """
162
+ Combine namespace and key to form a composite key (PK, SK).
163
+ """
164
+ namespace_str = ':'.join(namespace)
165
+ return (namespace_str, key)