Skip to content

Commit db87703

Browse files
Update Psycopg3 example to include non-admin and connection refresh logic (#113)
* Update Psycopg3 example to include non-admin and connection refresh logic * Update test workflow to match new example * Add explicit check for SSL certificate --------- Co-authored-by: Daniel Frankcom <frankcom@amazon.com>
1 parent 196663c commit db87703

6 files changed

Lines changed: 189 additions & 84 deletions

File tree

.github/workflows/python-psycopg3-integ-tests.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,20 @@ jobs:
5353
role-to-assume: ${{ secrets.PYTHON_IAM_ROLE }}
5454
aws-region: us-east-1
5555

56-
- name: Configure and run integration for psycopg3
56+
- name: Configure and run integration for psycopg3 - admin
5757
working-directory: ./python/psycopg
5858
env:
59+
CLUSTER_USER: "admin"
5960
CLUSTER_ENDPOINT: ${{ secrets.PYTHON_PSYCOPG3_CLUSTER_ENDPOINT }}
6061
REGION: ${{ secrets.PYTHON_PSYCOPG3_CLUSTER_REGION }}
6162
run: |
62-
python3 -m venv psycopg3-integ
63-
source psycopg3-integ/bin/activate
63+
python3 -m venv .venv
64+
source .venv/bin/activate
6465
pip install --upgrade pip
6566
pip install --force-reinstall -r requirements.txt
6667
python3 -c "import boto3; print(boto3.__version__)"
67-
pip install pytest pytest-cov
6868
pip list
6969
echo "$GITHUB_WORKSPACE" >> $GITHUB_PATH
70-
pytest -v test/
70+
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O root.pem
71+
python src/example.py
7172

python/psycopg/README.md

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,121 @@
1-
# Aurora DSQL psycopg code examples
1+
# Psycopg with Aurora DSQL
22

33
## Overview
44

5-
The code examples in this topic show you how to use the psycopg work with Aurora DSQL.
5+
This code example demonstrates how to use Psycopg (version 3) with Amazon Aurora SQL (DSQL). The example shows you how
6+
to connect to an Aurora DSQL cluster and perform basic database operations.
67

7-
## Run the examples
8+
Aurora DSQL is a distributed SQL database service that provides high availability and scalability for
9+
your PostgreSQL-compatible applications. Psycopg is a popular PostgreSQL adapter for Python that allows
10+
you to interact with PostgreSQL databases using Python code.
11+
12+
## About the code example
13+
14+
The example demonstrates a flexible connection approach that works for both admin and non-admin users:
15+
16+
* When connecting as an **admin user**, the example uses the `public` schema and generates an admin authentication
17+
token.
18+
* When connecting as a **non-admin user**, the example uses a custom `myschema` schema and generates a standard
19+
authentication token.
20+
21+
The code automatically detects the user type and adjusts its behavior accordingly.
22+
23+
## ⚠️ Important
24+
25+
* Running this code might result in charges to your AWS account.
26+
* We recommend that you grant your code least privilege. At most, grant only the
27+
minimum permissions required to perform the task. For more information, see
28+
[Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege).
29+
* This code is not tested in every AWS Region. For more information, see
30+
[AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services).
31+
32+
## Run the example
833

934
### Prerequisites
1035

11-
* python version >=3.8.0 is needed
36+
* You must have an AWS account, and have your default credentials and AWS Region
37+
configured as described in the
38+
[Globally configuring AWS SDKs and tools](https://docs.aws.amazon.com/credref/latest/refdocs/creds-config-files.html)
39+
guide.
40+
* [Python 3.8.0](https://www.python.org/) or later.
41+
* You must have an Aurora DSQL cluster. For information about creating an Aurora DSQL cluster, see the
42+
[Getting started with Aurora DSQL](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/getting-started.html)
43+
guide.
44+
* If connecting as a non-admin user, ensure the user is linked to an IAM role and is granted access to the `myschema`
45+
schema. See the
46+
[Using database roles with IAM roles](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/using-database-and-iam-roles.html)
47+
guide.
48+
49+
### Download the Amazon root certificate from the official trust store
50+
51+
Download the Amazon root certificate from the official trust store. This example shows one of the available certs that
52+
can be used by the client. Other certs such as AmazonRootCA2.pem, AmazonRootCA3.pem, etc. can also be used.
53+
54+
```
55+
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O root.pem
56+
```
57+
58+
### Set up environment for examples
59+
60+
1. Create and activate a Python virtual environment:
61+
62+
```bash
63+
python3 -m venv .venv
64+
source .venv/bin/activate # Linux, macOS
65+
# or
66+
.venv\Scripts\activate # Windows
67+
```
68+
69+
2. Install the required packages for running the examples:
70+
71+
```bash
72+
pip install -r requirements.txt
73+
```
74+
75+
### Run the code
76+
77+
The example demonstrates the following operations:
78+
79+
- Opening a connection to an Aurora DSQL cluster
80+
- Creating a table
81+
- Inserting and querying data
82+
83+
The example is designed to work with both admin and non-admin users:
84+
85+
- When run as an admin user, it uses the `public` schema
86+
- When run as a non-admin user, it uses the `myschema` schema
87+
88+
**Note:** running the example will use actual resources in your AWS account and may incur charges.
89+
90+
Set environment variables for your cluster details:
91+
92+
```bash
93+
# e.g. "admin"
94+
export CLUSTER_USER="<your user>"
1295

13-
### Setup test running environment
96+
# e.g. "foo0bar1baz2quux3quuux4.dsql.us-east-1.on.aws"
97+
export CLUSTER_ENDPOINT="<your endpoint>"
1498

15-
```sh
16-
source setup.sh
99+
# e.g. "us-east-1"
100+
export REGION="<your region>"
17101
```
18102

19-
### Run the example tests
103+
Run the example:
20104

21-
```sh
22-
# Use the account credentials dedicated for python
23-
export CLUSTER_ENDPOINT="<your cluster endpoint>"
24-
export REGION="<your cluster region>"
25-
pytest test/test_example.py
105+
```bash
106+
python src/example.py
26107
```
27108

28-
## Examples
109+
The example contains comments explaining the code and the operations being performed.
29110

30-
All examples are under test directory. The following table describes each test script.
111+
## Additional resources
31112

32-
| Test File | Description |
33-
| -------------------- | ----------- |
34-
| test_example.py | Show database table create, read and delete operation. |
113+
* [Amazon Aurora DSQL Documentation](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/what-is-aurora-dsql.html)
114+
* [Psycopg Documentation](https://www.psycopg.org/psycopg3/docs/)
115+
* [AWS SDK for Python (Boto3) Documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)
35116

36117
---
37118

38-
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
119+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
39120

40121
SPDX-License-Identifier: MIT-0

python/psycopg/requirements.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
psycopg[binary,pool]>=3
2-
pytest>=8
3-
botocore>=1.35.74
41
boto3>=1.35.74
5-
2+
psycopg[binary]>=3
3+
pytest>=8

python/psycopg/setup.sh

Lines changed: 0 additions & 13 deletions
This file was deleted.

python/psycopg/src/example.py

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,103 @@
1-
import psycopg
21
import boto3
3-
import os, sys
2+
import psycopg
3+
import os
4+
import sys
5+
46

5-
def main(cluster_endpoint, region):
6-
# Generate a password token
7+
def create_connection(cluster_user, cluster_endpoint, region):
8+
# Generate a fresh password token for each connection, to ensure the token is not expired
9+
# when the connection is established
710
client = boto3.client("dsql", region_name=region)
8-
# The token expiration time is optional, and the default value 900 seconds
9-
password_token = client.generate_db_connect_admin_auth_token(cluster_endpoint, region)
1011

11-
# connection parameters
12-
dbname = "dbname=postgres"
13-
user = "user=admin"
14-
host = f'host={cluster_endpoint}'
15-
sslmode = "sslmode=require"
16-
password = f'password={password_token}'
12+
if cluster_user == "admin":
13+
password_token = client.generate_db_connect_admin_auth_token(cluster_endpoint, region)
14+
else:
15+
password_token = client.generate_db_connect_auth_token(cluster_endpoint, region)
16+
17+
ssl_cert_path = "./root.pem"
18+
if not os.path.isfile(ssl_cert_path):
19+
raise FileNotFoundError(f"SSL certificate file not found: {ssl_cert_path}")
20+
21+
conn_params = {
22+
"dbname": "postgres",
23+
"user": cluster_user,
24+
"host": cluster_endpoint,
25+
"port": "5432",
26+
"sslmode": "verify-full",
27+
"sslrootcert": ssl_cert_path,
28+
"password": password_token
29+
}
1730

1831
# Make a connection to the cluster
19-
conn = psycopg.connect('%s %s %s %s %s' % (dbname, user, host, sslmode, password))
32+
conn = psycopg.connect(**conn_params)
2033

34+
if cluster_user == "admin":
35+
schema = "public"
36+
else:
37+
schema = "myschema"
38+
39+
try:
40+
with conn.cursor() as cur:
41+
cur.execute(f"SET search_path = {schema};")
42+
conn.commit()
43+
except Exception as e:
44+
conn.close()
45+
raise e
46+
47+
return conn
48+
49+
50+
def exercise_connection(conn):
2151
conn.set_autocommit(True)
2252

2353
cur = conn.cursor()
24-
25-
cur.execute("DROP TABLE IF EXISTS owner")
26-
27-
cur.execute(b"""
54+
55+
cur.execute("""
2856
CREATE TABLE IF NOT EXISTS owner(
2957
id uuid NOT NULL DEFAULT gen_random_uuid(),
3058
name varchar(30) NOT NULL,
3159
city varchar(80) NOT NULL,
3260
telephone varchar(20) DEFAULT NULL,
33-
PRIMARY KEY (id))"""
34-
)
61+
PRIMARY KEY (id))
62+
""")
3563

3664
# Insert some rows
37-
cur.execute("INSERT INTO owner(name, city, telephone) VALUES('John Doe', 'Anytown', '555-555-0150')")
65+
cur.execute("INSERT INTO owner(name, city, telephone) VALUES('John Doe', 'Anytown', '555-555-1999')")
3866

3967
cur.execute("SELECT * FROM owner WHERE name='John Doe'")
4068
row = cur.fetchone()
41-
42-
# Verify that the result we got is what we inserted before
69+
70+
# Verify the result we got is what we inserted before
4371
assert row[0] != None
4472
assert row[1] == "John Doe"
4573
assert row[2] == "Anytown"
46-
assert row[3] == "555-555-0150"
47-
48-
# Insert some rows
49-
# Placing this cleanup the table after the example. If we run the example
50-
# again we do not have to worry about data inserted by previous runs
74+
assert row[3] == "555-555-1999"
75+
76+
# Clean up the table after the example. If we run the example again
77+
# we do not have to worry about data inserted by previous runs
5178
cur.execute("DELETE FROM owner where name = 'John Doe'")
5279

80+
81+
def main():
82+
conn = None
83+
try:
84+
cluster_user = os.environ.get("CLUSTER_USER", None)
85+
assert cluster_user is not None, "CLUSTER_USER environment variable is not set"
86+
87+
cluster_endpoint = os.environ.get("CLUSTER_ENDPOINT", None)
88+
assert cluster_endpoint is not None, "CLUSTER_ENDPOINT environment variable is not set"
89+
90+
region = os.environ.get("REGION", None)
91+
assert region is not None, "REGION environment variable is not set"
92+
93+
conn = create_connection(cluster_user, cluster_endpoint, region)
94+
exercise_connection(conn)
95+
finally:
96+
if conn is not None:
97+
conn.close()
98+
99+
print("Connection exercised successfully")
100+
101+
53102
if __name__ == "__main__":
54-
cluster_endpoint = os.environ.get("CLUSTER_ENDPOINT", None)
55-
region = os.environ.get("REGION", None)
56-
if cluster_endpoint is None:
57-
sys.exit("CLUSTER_ENDPOINT environment variable is not set")
58-
if region is None:
59-
sys.exit("REGION environment variable is not set")
60-
main(cluster_endpoint, region)
103+
main()
Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
from example import main
2+
23
import pytest
3-
import os, sys
4+
45

56
# Smoke tests that our example works fine
67
def test_example():
78
try:
8-
cluster_endpoint = os.environ.get("CLUSTER_ENDPOINT", None)
9-
region = os.environ.get("REGION", None)
10-
if cluster_endpoint is None:
11-
sys.exit("CLUSTER_ENDPOINT environment variable is not set")
12-
if region is None:
13-
sys.exit("REGION environment variable is not set")
14-
main(cluster_endpoint, region)
9+
main()
1510
except Exception as e:
16-
pytest.fail("Unexpected exception: " + str(e))
11+
pytest.fail(f"Unexpected exception: {e}")

0 commit comments

Comments
 (0)