Skip to content
This repository has been archived by the owner on Aug 5, 2024. It is now read-only.

Commit

Permalink
Feature/RT-81 Persistence Adapter (#44)
Browse files Browse the repository at this point in the history
* Added aiboto3 to Pipfile for dynamodb persistence adaptor

* Updated interface for persistence adaptor.

* Added dynamodb flavour of persistence adaptor and included unit tests.

* Updated Pipfile.lock for pipeline usage.

* Renamed 'adapter' to 'adaptor'.

* Addressed sonar issues.

* Resolved review comments.
  • Loading branch information
flavgj authored Aug 8, 2019
1 parent 02ab2c0 commit 2d6a71e
Show file tree
Hide file tree
Showing 10 changed files with 561 additions and 17 deletions.
1 change: 1 addition & 0 deletions mhs/Pipfile
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ requests = "*"
integration-adaptors-common = {editable = true,path = "./../common"}
ldap3 = "*"
tornado = "*"
aioboto3 = "*"
defusedxml = "~=0.6"

[requires]
Expand Down
176 changes: 175 additions & 1 deletion mhs/Pipfile.lock
100644 → 100755

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

115 changes: 115 additions & 0 deletions mhs/mhs/common/state/dynamo_persistence_adaptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Module containing functionality for a DynamoDB implementation of a persistence adaptor."""
import contextlib
import json
import traceback

import aioboto3
import utilities.integration_adaptors_logger as log

import mhs.common.state.persistence_adaptor

logger = log.IntegrationAdaptorsLogger('DYNAMO_PERSISTENCE')


class RecordCreationError(RuntimeError):
"""Error occurred when creating record."""
pass


class RecordDeletionError(RuntimeError):
"""Error occurred when deleting record."""
pass


class RecordRetrievalError(RuntimeError):
"""Error occurred when retrieving record."""
pass


class DynamoPersistenceAdaptor(mhs.common.state.persistence_adaptor.PersistenceAdaptor):
"""Class responsible for persisting items into a DynamoDB."""

def __init__(self, **kwargs):
"""
Constructs a DynamoDB version of a
:class:`PersistenceAdaptor <mhs.common.state.persistence_adaptor.PersistenceAdaptor>`.
The kwargs provided should contain the following information:
* table_name: The Table Name used to identify the dynamo table containing required items.
:param kwargs: The key word arguments required for this constructor.
"""
self.table_name = kwargs.get('table_name')

async def add(self, key, data):
"""Add an item to a specified table, using a provided key.
:param key: The key used to identify the item.
:param data: The item to store in persistence.
:return: The previous version of the item which has been replaced. (None if no previous item)
"""
logger.info('011', 'Adding {record} for {key}', {'record': data, 'key': key})
try:
async with self.__create_dynamo_table() as table:
response = await table.put_item(
Item={'key': key, 'data': json.dumps(data)},
ReturnValues='ALL_OLD'
)
if response.get('Attributes', {}).get('data') is None:
logger.info('000', 'No previous record found: {key}', {'key': key})
return None
return json.loads(response.get('Attributes', {}).get('data'))
except Exception as e:
logger.error('001', 'Error creating record: {exception}', {'exception': traceback.format_exc()})
raise RecordCreationError from e

async def get(self, key):
"""
Retrieves an item from a specified table with a given key.
:param key: The key which identifies the item to get.
:return: The item from the specified table with the given key. (None if no item found)
"""
logger.info('002', 'Getting record for {key}', {'key': key})
try:
async with self.__create_dynamo_table() as table:
response = await table.get_item(
Key={'key': key}
)
logger.info('003', 'Response from get_item call: {response}', {'response': response})
if 'Item' not in response:
logger.info('004', 'No item found for record: {key}', {'key': key})
return None
return json.loads(response.get('Item', {}).get('data'))
except Exception as e:
logger.error('005', 'Error getting record: {exception}', {'exception': traceback.format_exc()})
raise RecordRetrievalError from e

async def delete(self, key):
"""
Removes an item from a table given it's key.
:param key: The key of the item to delete.
:return: The instance of the item which has been deleted from persistence. (None if no item found)
"""
logger.info('006', 'Deleting record for {key}', {'key': key})
try:
async with self.__create_dynamo_table() as table:
response = await table.delete_item(
Key={'key': key},
ReturnValues='ALL_OLD'
)
logger.info('007', 'Response from delete_item call: {response}', {'response': response})
if 'Attributes' not in response:
logger.info('008', 'No values found for record: {key}', {'key': key})
return None
return json.loads(response.get('Attributes', {}).get('data'))
except Exception as e:
logger.error('009', 'Error deleting record: {exception}', {'exception': traceback.format_exc()})
raise RecordDeletionError from e

@contextlib.asynccontextmanager
async def __create_dynamo_table(self):
"""
Creates a connection to the table referenced by this instance.
:return: The table to be used by this instance.
"""
async with aioboto3.resource('dynamodb', region_name='eu-west-2') as dynamo_resource:
logger.info('010', 'Establishing connection to {table_name}', {'table_name': self.table_name})
yield dynamo_resource.Table(self.table_name)
8 changes: 0 additions & 8 deletions mhs/mhs/common/state/persistence_adapter.py

This file was deleted.

Loading

0 comments on commit 2d6a71e

Please sign in to comment.