A very simplistic AWS Aurora Serverless Data API mapper
Project description
Data API Mapper
The Data API Mapper is a lightweight wrapper for Amazon Aurora Serverless Data API. It's STRONGLY inspired by DataApiClient. Currently, it only maps PostgreSQL types, but it can be easily extended to add MySQL.
Motivation
Check https://github.com/jeremydaly/data-api-client#why-do-i-need-this
How to use this module
import os
import boto3
from data_api_mapper import DataAPIClient
db_name = os.getenv('DB_NAME')
db_cluster_arn = os.getenv('DB_CLUSTER_ARN')
secret_arn = os.getenv('SECRET_ARN')
rds_client = boto3.client('rds-data')
data_client = DataAPIClient(rds_client, secret_arn, db_cluster_arn, db_name)
Running a query
Once initialized, running a query is super simple. Use the query()
method and pass in your SQL statement:
result = data_client.query('SELECT * FROM myTable')
By default, this will return your rows as an array of dictionaries with column names as key names and the values as values, converted to python types: For example, for this database:
CREATE TABLE aurora_data_api_test (
id SERIAL,
a_name TEXT,
doc JSONB DEFAULT '{}',
num_numeric NUMERIC (10, 5) DEFAULT 0.0,
num_float float,
num_integer integer,
ts TIMESTAMP WITH TIME ZONE,
field_string_null TEXT NULL,
field_long_null integer NULL,
field_doc_null JSONB NULL,
field_boolean BOOLEAN NULL,
tz_notimezone TIMESTAMP,
a_date DATE
);
INSERT INTO aurora_data_api_test (a_name, doc, num_numeric, num_float, num_integer, ts, tz_notimezone, a_date)
VALUES ('first row', '{"string_vale": "string1", "int_value": 1, "float_value": 1.11}', 1.12345, 1.11, 1, '1976-11-02 08:45:00 UTC', '2021-03-03 15:51:48.082288', '1976-11-02');
VALUES ('second row', '{"string_vale": "string2", "int_value": 2, "float_value": 2.22}', 2.22, 2.22, 2, '1976-11-02 08:45:00 UTC', '2021-03-03 15:51:48.082288', '1976-11-02');
this query
self.data_client.query("select * from aurora_data_api_test where id (1,2)"
will return:
[{'id': 1, 'a_name': 'first row', 'doc': {'int_value': 1, 'float_value': 1.11, 'string_vale': 'string1'}, 'num_numeric': Decimal('1.12345'), 'num_float': 1.11, 'num_integer': 1, 'ts': datetime.datetime(1976, 11, 2, 8, 45, tzinfo=datetime.timezone.utc), 'field_string_null': None, 'field_long_null': None, 'field_doc_null': None, 'field_boolean': None, 'tz_notimezone': datetime.datetime(2021, 3, 3, 15, 51, 48, 82288, tzinfo=datetime.timezone.utc), 'a_date': datetime.date(1976, 11, 2)},
{'id': 2, 'a_name': 'prueba', 'doc': {'a_date': '1976-11-02', 'num_int': 1, 'num_float': 45.6, 'somestring': 'hello'}, 'num_numeric': Decimal('100.76540'), 'num_float': 10.123, 'num_integer': 1, 'ts': datetime.datetime(1976, 11, 2, 8, 45, tzinfo=datetime.timezone.utc), 'field_string_null': None, 'field_long_null': None, 'field_doc_null': None, 'field_boolean': True, 'tz_notimezone': datetime.datetime(2021, 3, 3, 15, 51, 48, 82288, tzinfo=datetime.timezone.utc), 'a_date': datetime.date(1976, 11, 2)}]
By default, query()
receives a dictionary that maps PostgreSQL types to python types.
POSTGRES_PYTHON_MAPPER = {
'jsonb': JsonbToDict,
'timestamptz': TimestampzToDatetimeUTC,
'timestamp': TimestampzToDatetimeUTC,
'date': DateToDate,
'numeric': NumericToDecimal,
}
class DataAPIClient:
def __init__(self, rds_client, secret_arn, cluster_arn, database_name) -> None:
...
def query(self, sql, parameters=None, mapper=POSTGRES_PYTHON_MAPPER):
...
There is also a mapper for AppSync, you can check the mappers here.
If you use MySQL you need a mapper.
Running a query with parameters
To query with parameters, you can use named parameters in your SQL, and then provide an object containing your parameters as the second argument to the query()
method and the client does the conversion for you:
import datetime
result = data_client.query(
'SELECT * FROM myTable WHERE id = :id AND created > :createDate',
{ 'id': 2, 'createDate': datetime.date(2021,6,1) }
)
For all the conversions, check here
Transactions
class TestDataAPI(unittest.TestCase):
data_client = None
@classmethod
def setUpClass(cls):
db_name = os.getenv('DB_NAME')
db_cluster_arn = os.getenv('DB_CLUSTER_ARN')
secret_arn = os.getenv('SECRET_ARN')
rds_client = boto3.client('rds-data')
data_client = DataAPIClient(rds_client, secret_arn, db_cluster_arn, db_name)
initial_sql = """
DROP TABLE IF EXISTS aurora_data_api_test;
CREATE TABLE aurora_data_api_test (
id SERIAL,
a_name TEXT,
doc JSONB DEFAULT '{}',
num_numeric NUMERIC (10, 5) DEFAULT 0.0,
num_float float,
num_integer integer,
ts TIMESTAMP WITH TIME ZONE,
field_string_null TEXT NULL,
field_long_null integer NULL,
field_doc_null JSONB NULL,
field_boolean BOOLEAN NULL,
tz_notimezone TIMESTAMP,
a_date DATE
);
INSERT INTO aurora_data_api_test (a_name, doc, num_numeric, num_float, num_integer, ts, tz_notimezone, a_date)
VALUES ('first row', '{"string_vale": "string1", "int_value": 1, "float_value": 1.11}', 1.12345, 1.11, 1, '1976-11-02 08:45:00 UTC', '2021-03-03 15:51:48.082288', '1976-11-02');
VALUES ('second row', '{"string_vale": "string2", "int_value": 2, "float_value": 2.22}', 2.22, 2.22, 2, '1976-11-02 08:45:00 UTC', '2021-03-03 15:51:48.082288', '1976-11-02');
"""
data_client.query(sql=initial_sql)
cls.data_client = data_client
def test_transaction(self):
transaction = self.data_client.begin_transaction()
transaction.query('''
INSERT INTO aurora_data_api_test (id, a_name, doc, num_numeric, num_float, num_integer, ts, tz_notimezone)
VALUES (345, 'first row', '{"string_vale": "string1", "int_value": 1, "float_value": 1.11}', 1.12345, 1.11, 1, '1976-11-02 08:45:00 UTC', '2021-03-03 15:51:48.082288');
''')
inside_transaction = transaction.query("select * from aurora_data_api_test where id = 345")
self.assertEqual(1, len(inside_transaction))
transaction.query('''
INSERT INTO aurora_data_api_test (id, a_name, doc, num_numeric, num_float, num_integer, ts, tz_notimezone)
VALUES (346, 'first row', '{"string_vale": "string1", "int_value": 1, "float_value": 1.11}', 1.12345, 1.11, 1, '1976-11-02 08:45:00 UTC', '2021-03-03 15:51:48.082288');
''')
inside_transaction = transaction.query("select * from aurora_data_api_test where id in (345,346)")
self.assertEqual(2, len(inside_transaction))
before_commit = self.data_client.query("select * from aurora_data_api_test where id in (345,346)")
self.assertEqual(0, len(before_commit))
transaction.commit()
after_commit = self.data_client.query("select * from aurora_data_api_test where id in (345,346)")
self.assertEqual(2, len(after_commit))
def test_transaction_rollback(self):
transaction = self.data_client.begin_transaction()
transaction.query('''
INSERT INTO aurora_data_api_test (id, a_name, doc, num_numeric, num_float, num_integer, ts, tz_notimezone)
VALUES (355, 'first row', '{"string_vale": "string1", "int_value": 1, "float_value": 1.11}', 1.12345, 1.11, 1, '1976-11-02 08:45:00 UTC', '2021-03-03 15:51:48.082288')
''')
inside_transaction = transaction.query("select * from aurora_data_api_test where id = 355")
self.assertEqual(1, len(inside_transaction))
transaction.query('''
INSERT INTO aurora_data_api_test (id, a_name, doc, num_numeric, num_float, num_integer, ts, tz_notimezone)
VALUES (356, 'first row', '{"string_vale": "string1", "int_value": 1, "float_value": 1.11}', 1.12345, 1.11, 1, '1976-11-02 08:45:00 UTC', '2021-03-03 15:51:48.082288')
''')
inside_transaction = transaction.query("select * from aurora_data_api_test where id in (355,356)")
self.assertEqual(2, len(inside_transaction))
before_rollback = self.data_client.query("select * from aurora_data_api_test where id in (355,356)")
self.assertEqual(0, len(before_rollback))
transaction.rollback()
after_rollback = self.data_client.query("select * from aurora_data_api_test where id in (355,356)")
self.assertEqual(0, len(after_rollback))
@classmethod
def tearDownClass(cls):
cls.data_client.query('DROP TABLE IF EXISTS aurora_data_api_test')
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for data_api_mapper-1.2.4-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fb067edadda7aabae115338afd422babb848b40aa4b93315d6e7590b73a5442a |
|
MD5 | 85e15dddb75eff3f9f5bf77da1355e7c |
|
BLAKE2b-256 | 47b9a8c6bbd5d0fdcf5a1a2fa3bb5f21af695551a31fba2482673274037ccc34 |