1414
1515
1616from memoized_property import memoized_property
17- import boto .dynamodb2
18- from boto .dynamodb2 .table import Table
19- from boto .dynamodb2 .exceptions import ConditionalCheckFailedException
17+ import boto3
18+ from botocore .exceptions import ClientError
2019import os
2120from datalake .common .errors import InsufficientConfiguration
2221import logging
@@ -42,50 +41,69 @@ def from_config(cls):
4241
4342 def _prepare_connection (self , connection ):
4443 self .logger .info ("Preparing connection..." )
45- region = os .environ .get ('AWS_REGION' )
44+ region = os .environ .get ('AWS_REGION' ) or 'us-west-1' # Default region if not set
4645 if connection :
46+ # When connection is provided from outside, we need to ensure _client is set
4747 self ._connection = connection
48- elif region :
49- self ._connection = boto .dynamodb2 .connect_to_region (region )
48+
49+ # Check if _connection has a client attribute (added in our tests)
50+ # or create a new client if it doesn't
51+ if hasattr (connection , 'client' ):
52+ self ._client = connection .client
53+ else :
54+ # Create a new client for the same region
55+ self ._client = boto3 .client ('dynamodb' , region_name = region )
5056 else :
51- msg = 'Please provide a connection or configure a region'
52- raise InsufficientConfiguration (msg )
57+ # Create both resource and client
58+ self ._connection = boto3 .resource ('dynamodb' , region_name = region )
59+ self ._client = boto3 .client ('dynamodb' , region_name = region )
5360
5461 @memoized_property
5562 def _table (self ):
56- return Table (self .table_name , connection = self . _connection )
57-
63+ return self . _connection . Table (self .table_name )
64+
5865 @memoized_property
5966 def _latest_table (self ):
60- return Table (self .latest_table_name , connection = self . _connection )
67+ return self . _connection . Table (self .latest_table_name )
6168
6269 def store (self , record ):
6370 try :
64- self ._table .put_item (data = record )
65- except ConditionalCheckFailedException :
66- # Tolerate duplicate stores
67- pass
71+ # In boto3, the parameter is Item, not data
72+ self ._table .put_item (Item = record )
73+ except ClientError as e :
74+ if e .response ['Error' ]['Code' ] == 'ConditionalCheckFailedException' :
75+ # Tolerate duplicate stores
76+ pass
77+ else :
78+ raise
6879 if self .latest_table_name :
6980 self .store_latest (record )
7081
7182 def update (self , record ):
72- self ._table .put_item (data = record , overwrite = True )
83+ # In boto3, there's no overwrite parameter, but it's the default behavior
84+ self ._table .put_item (Item = record )
7385
7486 def store_latest (self , record ):
7587 """
7688 Record must utilize AttributeValue syntax
7789 for the conditional put.
7890 """
79- condition_expression = " attribute_not_exists(what_where_key) OR metadata.#metadata_start <= :new_start"
91+ # Boto3 requires different parameter naming: condition_expression -> ConditionExpression
92+ condition_expression = "attribute_not_exists(what_where_key) OR metadata.#metadata_start <= :new_start"
93+
94+ # For DynamoDB client (not resource), we need to use typed dictionaries for expression attribute values
95+ # The client.put_item method requires typed attribute values, unlike the resource-level API
8096 expression_attribute_values = {
81- ':new_start' : {'N' : str (record ['metadata' ]['start' ])}
97+ ':new_start' : {'N' : str (record ['metadata' ]['start' ])} # Must use typed dict here
8298 }
8399
84- # aliases for DynamoDB reserved names.
100+ # aliases for DynamoDB reserved names - parameter name doesn't change
85101 expression_attribute_names = {
86102 '#metadata_start' : "start"
87103 }
88104
105+ # In boto3, we need to follow the same explicit typing for now
106+ # since this is using the low-level API
89107 if record ['metadata' ]['work_id' ] is None :
90108 work_id_value = {'NULL' : True }
91109 else :
@@ -96,7 +114,8 @@ def store_latest(self, record):
96114 else :
97115 end_time_value = {'N' : str (record ['metadata' ]['end' ])}
98116
99- record = {
117+ # Format is the same for low-level API
118+ formatted_record = {
100119 'what_where_key' : {"S" : record ['metadata' ]['what' ]+ ':' + record ['metadata' ]['where' ]},
101120 'time_index_key' : {"S" : record ['time_index_key' ]},
102121 'range_key' : {"S" : record ['range_key' ]},
@@ -130,19 +149,25 @@ def store_latest(self, record):
130149 'url' : {"S" : record ['url' ]},
131150 'create_time' : {'N' : str (record ['create_time' ])}
132151 }
133- self .logger .info (f"Attempting to store record: { record } " )
152+ self .logger .info (f"Attempting to store record: { formatted_record } " )
134153 try :
135- self ._connection .put_item (
136- table_name = self .latest_table_name ,
137- item = record ,
138- condition_expression = condition_expression ,
139- expression_attribute_names = expression_attribute_names ,
140- expression_attribute_values = expression_attribute_values ,
154+ # Boto3 uses _client instead of _connection, and all parameters are CamelCase instead of snake_case
155+ self ._client .put_item (
156+ TableName = self .latest_table_name , # table_name -> TableName
157+ Item = formatted_record , # item -> Item
158+ ConditionExpression = condition_expression , # condition_expression -> ConditionExpression
159+ ExpressionAttributeNames = expression_attribute_names , # expression_attribute_names -> ExpressionAttributeNames
160+ ExpressionAttributeValues = expression_attribute_values , # expression_attribute_values -> ExpressionAttributeValues
141161 )
142162 self .logger .info ("Record stored successfully." )
143- except ConditionalCheckFailedException :
144- self .logger .debug (f"Condition not met for record { record } ,"
163+ # Boto3 uses ClientError instead of ConditionalCheckFailedException
164+ except ClientError as e :
165+ if e .response ['Error' ]['Code' ] == 'ConditionalCheckFailedException' :
166+ self .logger .debug (f"Condition not met for record { formatted_record } ,"
145167 "no operation was performed." )
168+ else :
169+ self .logger .error (f"Error occurred while attempting { formatted_record } : { str (e )} " )
170+ raise
146171 except Exception as e :
147- self .logger .error (f"Error occurred while attempting { record } : { str (e )} " )
172+ self .logger .error (f"Error occurred while attempting { formatted_record } : { str (e )} " )
148173
0 commit comments