-
Notifications
You must be signed in to change notification settings - Fork 20
Update Psycopg3 example to include non-admin and connection refresh logic #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,121 @@ | ||
| # Aurora DSQL psycopg code examples | ||
| # Psycopg with Aurora DSQL | ||
|
|
||
| ## Overview | ||
|
|
||
| The code examples in this topic show you how to use the psycopg work with Aurora DSQL. | ||
| This code example demonstrates how to use Psycopg (version 3) with Amazon Aurora SQL (DSQL). The example shows you how | ||
| to connect to an Aurora DSQL cluster and perform basic database operations. | ||
|
|
||
| ## Run the examples | ||
| Aurora DSQL is a distributed SQL database service that provides high availability and scalability for | ||
| your PostgreSQL-compatible applications. Psycopg is a popular PostgreSQL adapter for Python that allows | ||
| you to interact with PostgreSQL databases using Python code. | ||
|
|
||
| ## About the code example | ||
|
|
||
| The example demonstrates a flexible connection approach that works for both admin and non-admin users: | ||
|
|
||
| * When connecting as an **admin user**, the example uses the `public` schema and generates an admin authentication | ||
| token. | ||
| * When connecting as a **non-admin user**, the example uses a custom `myschema` schema and generates a standard | ||
| authentication token. | ||
|
|
||
| The code automatically detects the user type and adjusts its behavior accordingly. | ||
|
|
||
| ## ⚠️ Important | ||
|
|
||
| * Running this code might result in charges to your AWS account. | ||
| * We recommend that you grant your code least privilege. At most, grant only the | ||
| minimum permissions required to perform the task. For more information, see | ||
| [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). | ||
| * This code is not tested in every AWS Region. For more information, see | ||
| [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). | ||
|
|
||
| ## Run the example | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| * python version >=3.8.0 is needed | ||
| * You must have an AWS account, and have your default credentials and AWS Region | ||
| configured as described in the | ||
| [Globally configuring AWS SDKs and tools](https://docs.aws.amazon.com/credref/latest/refdocs/creds-config-files.html) | ||
| guide. | ||
| * [Python 3.8.0](https://www.python.org/) or later. | ||
| * You must have an Aurora DSQL cluster. For information about creating an Aurora DSQL cluster, see the | ||
| [Getting started with Aurora DSQL](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/getting-started.html) | ||
| guide. | ||
| * If connecting as a non-admin user, ensure the user is linked to an IAM role and is granted access to the `myschema` | ||
| schema. See the | ||
| [Using database roles with IAM roles](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/using-database-and-iam-roles.html) | ||
| guide. | ||
|
|
||
| ### Download the Amazon root certificate from the official trust store | ||
|
|
||
| Download the Amazon root certificate from the official trust store. This example shows one of the available certs that | ||
| can be used by the client. Other certs such as AmazonRootCA2.pem, AmazonRootCA3.pem, etc. can also be used. | ||
|
|
||
| ``` | ||
| wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O root.pem | ||
| ``` | ||
|
|
||
| ### Set up environment for examples | ||
|
|
||
| 1. Create and activate a Python virtual environment: | ||
|
|
||
| ```bash | ||
| python3 -m venv .venv | ||
| source .venv/bin/activate # Linux, macOS | ||
| # or | ||
| .venv\Scripts\activate # Windows | ||
| ``` | ||
|
|
||
| 2. Install the required packages for running the examples: | ||
|
|
||
| ```bash | ||
| pip install -r requirements.txt | ||
| ``` | ||
|
|
||
| ### Run the code | ||
|
|
||
| The example demonstrates the following operations: | ||
|
|
||
| - Opening a connection to an Aurora DSQL cluster | ||
| - Creating a table | ||
| - Inserting and querying data | ||
|
|
||
| The example is designed to work with both admin and non-admin users: | ||
|
|
||
| - When run as an admin user, it uses the `public` schema | ||
| - When run as a non-admin user, it uses the `myschema` schema | ||
|
|
||
| **Note:** running the example will use actual resources in your AWS account and may incur charges. | ||
|
|
||
| Set environment variables for your cluster details: | ||
|
|
||
| ```bash | ||
| # e.g. "admin" | ||
| export CLUSTER_USER="<your user>" | ||
|
|
||
| ### Setup test running environment | ||
| # e.g. "foo0bar1baz2quux3quuux4.dsql.us-east-1.on.aws" | ||
| export CLUSTER_ENDPOINT="<your endpoint>" | ||
|
|
||
| ```sh | ||
| source setup.sh | ||
| # e.g. "us-east-1" | ||
| export REGION="<your region>" | ||
| ``` | ||
|
|
||
| ### Run the example tests | ||
| Run the example: | ||
|
|
||
| ```sh | ||
| # Use the account credentials dedicated for python | ||
| export CLUSTER_ENDPOINT="<your cluster endpoint>" | ||
| export REGION="<your cluster region>" | ||
| pytest test/test_example.py | ||
| ```bash | ||
| python src/example.py | ||
| ``` | ||
|
|
||
| ## Examples | ||
| The example contains comments explaining the code and the operations being performed. | ||
|
|
||
| All examples are under test directory. The following table describes each test script. | ||
| ## Additional resources | ||
|
|
||
| | Test File | Description | | ||
| | -------------------- | ----------- | | ||
| | test_example.py | Show database table create, read and delete operation. | | ||
| * [Amazon Aurora DSQL Documentation](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/what-is-aurora-dsql.html) | ||
| * [Psycopg Documentation](https://www.psycopg.org/psycopg3/docs/) | ||
| * [AWS SDK for Python (Boto3) Documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) | ||
|
|
||
| --- | ||
|
|
||
| Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: MIT-0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,3 @@ | ||
| psycopg[binary,pool]>=3 | ||
| pytest>=8 | ||
| botocore>=1.35.74 | ||
| boto3>=1.35.74 | ||
|
|
||
| psycopg[binary]>=3 | ||
| pytest>=8 |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,60 +1,103 @@ | ||
| import psycopg | ||
| import boto3 | ||
| import os, sys | ||
| import psycopg | ||
| import os | ||
| import sys | ||
|
|
||
|
|
||
| def main(cluster_endpoint, region): | ||
| # Generate a password token | ||
| def create_connection(cluster_user, cluster_endpoint, region): | ||
| # Generate a fresh password token for each connection, to ensure the token is not expired | ||
| # when the connection is established | ||
| client = boto3.client("dsql", region_name=region) | ||
| # The token expiration time is optional, and the default value 900 seconds | ||
| password_token = client.generate_db_connect_admin_auth_token(cluster_endpoint, region) | ||
|
|
||
| # connection parameters | ||
| dbname = "dbname=postgres" | ||
| user = "user=admin" | ||
| host = f'host={cluster_endpoint}' | ||
| sslmode = "sslmode=require" | ||
| password = f'password={password_token}' | ||
| if cluster_user == "admin": | ||
| password_token = client.generate_db_connect_admin_auth_token(cluster_endpoint, region) | ||
| else: | ||
| password_token = client.generate_db_connect_auth_token(cluster_endpoint, region) | ||
|
|
||
| ssl_cert_path = "./root.pem" | ||
| if not os.path.isfile(ssl_cert_path): | ||
| raise FileNotFoundError(f"SSL certificate file not found: {ssl_cert_path}") | ||
|
danielfrankcom marked this conversation as resolved.
|
||
|
|
||
| conn_params = { | ||
| "dbname": "postgres", | ||
| "user": cluster_user, | ||
| "host": cluster_endpoint, | ||
| "port": "5432", | ||
| "sslmode": "verify-full", | ||
| "sslrootcert": ssl_cert_path, | ||
| "password": password_token | ||
| } | ||
|
|
||
| # Make a connection to the cluster | ||
| conn = psycopg.connect('%s %s %s %s %s' % (dbname, user, host, sslmode, password)) | ||
| conn = psycopg.connect(**conn_params) | ||
|
|
||
| if cluster_user == "admin": | ||
| schema = "public" | ||
| else: | ||
| schema = "myschema" | ||
|
|
||
| try: | ||
| with conn.cursor() as cur: | ||
| cur.execute(f"SET search_path = {schema};") | ||
| conn.commit() | ||
| except Exception as e: | ||
| conn.close() | ||
| raise e | ||
|
|
||
| return conn | ||
|
|
||
|
|
||
| def exercise_connection(conn): | ||
| conn.set_autocommit(True) | ||
|
|
||
| cur = conn.cursor() | ||
|
|
||
| cur.execute("DROP TABLE IF EXISTS owner") | ||
|
|
||
| cur.execute(b""" | ||
|
|
||
| cur.execute(""" | ||
| CREATE TABLE IF NOT EXISTS owner( | ||
| id uuid NOT NULL DEFAULT gen_random_uuid(), | ||
| name varchar(30) NOT NULL, | ||
| city varchar(80) NOT NULL, | ||
| telephone varchar(20) DEFAULT NULL, | ||
| PRIMARY KEY (id))""" | ||
| ) | ||
| PRIMARY KEY (id)) | ||
| """) | ||
|
|
||
| # Insert some rows | ||
| cur.execute("INSERT INTO owner(name, city, telephone) VALUES('John Doe', 'Anytown', '555-555-0150')") | ||
| cur.execute("INSERT INTO owner(name, city, telephone) VALUES('John Doe', 'Anytown', '555-555-1999')") | ||
|
|
||
| cur.execute("SELECT * FROM owner WHERE name='John Doe'") | ||
| row = cur.fetchone() | ||
| # Verify that the result we got is what we inserted before | ||
|
|
||
| # Verify the result we got is what we inserted before | ||
| assert row[0] != None | ||
| assert row[1] == "John Doe" | ||
| assert row[2] == "Anytown" | ||
| assert row[3] == "555-555-0150" | ||
|
|
||
| # Insert some rows | ||
| # Placing this cleanup the table after the example. If we run the example | ||
| # again we do not have to worry about data inserted by previous runs | ||
| assert row[3] == "555-555-1999" | ||
|
|
||
| # Clean up the table after the example. If we run the example again | ||
| # we do not have to worry about data inserted by previous runs | ||
| cur.execute("DELETE FROM owner where name = 'John Doe'") | ||
|
|
||
|
|
||
| def main(): | ||
| conn = None | ||
| try: | ||
| cluster_user = os.environ.get("CLUSTER_USER", None) | ||
| assert cluster_user is not None, "CLUSTER_USER environment variable is not set" | ||
|
|
||
| cluster_endpoint = os.environ.get("CLUSTER_ENDPOINT", None) | ||
| assert cluster_endpoint is not None, "CLUSTER_ENDPOINT environment variable is not set" | ||
|
|
||
| region = os.environ.get("REGION", None) | ||
| assert region is not None, "REGION environment variable is not set" | ||
|
|
||
| conn = create_connection(cluster_user, cluster_endpoint, region) | ||
| exercise_connection(conn) | ||
| finally: | ||
| if conn is not None: | ||
| conn.close() | ||
|
|
||
| print("Connection exercised successfully") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| cluster_endpoint = os.environ.get("CLUSTER_ENDPOINT", None) | ||
| region = os.environ.get("REGION", None) | ||
| if cluster_endpoint is None: | ||
| sys.exit("CLUSTER_ENDPOINT environment variable is not set") | ||
| if region is None: | ||
| sys.exit("REGION environment variable is not set") | ||
| main(cluster_endpoint, region) | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,11 @@ | ||
| from example import main | ||
|
|
||
| import pytest | ||
| import os, sys | ||
|
|
||
|
|
||
| # Smoke tests that our example works fine | ||
| def test_example(): | ||
| try: | ||
| cluster_endpoint = os.environ.get("CLUSTER_ENDPOINT", None) | ||
| region = os.environ.get("REGION", None) | ||
| if cluster_endpoint is None: | ||
| sys.exit("CLUSTER_ENDPOINT environment variable is not set") | ||
| if region is None: | ||
| sys.exit("REGION environment variable is not set") | ||
| main(cluster_endpoint, region) | ||
| main() | ||
| except Exception as e: | ||
| pytest.fail("Unexpected exception: " + str(e)) | ||
| pytest.fail(f"Unexpected exception: {e}") |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.