├── .devcontainer ├── Dockerfile ├── devcontainer.json └── docker-compose.yaml ├── .env.azure ├── .env.devcontainer ├── .github ├── dependabot-bot.yml ├── dependabot.yaml └── workflows │ └── tests.yaml ├── .gitignore ├── README.md ├── main_psycopg.py ├── main_sqlalchemy.py ├── pyproject.toml ├── requirements.txt └── restaurants.csv /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG IMAGE=bullseye 2 | FROM mcr.microsoft.com/devcontainers/${IMAGE} 3 | 4 | ENV PYTHONUNBUFFERED 1 5 | 6 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 7 | && apt-get -y install --no-install-recommends postgresql-client \ 8 | && apt-get clean -y && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/python-3 3 | { 4 | "name": "python-db-copilot", 5 | "dockerComposeFile": "docker-compose.yaml", 6 | "service": "app", 7 | "workspaceFolder": "/workspace", 8 | "forwardPorts": [5432], 9 | "portsAttributes": { 10 | "5432": {"label": "PostgreSQL port", "onAutoForward": "silent"} 11 | }, 12 | // Configure tool-specific properties. 13 | "customizations": { 14 | // Configure properties specific to VS Code. 15 | "vscode": { 16 | // Add the IDs of extensions you want installed when the container is created. 17 | "extensions": [ 18 | "ms-python.python", 19 | "ms-python.vscode-pylance", 20 | "charliermarsh.ruff", 21 | "ms-python.black-formatter", 22 | "mtxr.sqltools", 23 | "mtxr.sqltools-driver-pg", 24 | "ms-vscode.vscode-node-azure-pack", 25 | "mechatroner.rainbow-csv" 26 | ], 27 | // Set *default* container specific settings.json values on container create. 28 | "settings": { 29 | "python.defaultInterpreterPath": "/usr/local/bin/python", 30 | "python.testing.unittestEnabled": false, 31 | "python.testing.pytestEnabled": false, 32 | "[python]": { 33 | "editor.formatOnSave": true, 34 | "editor.codeActionsOnSave": { 35 | "source.fixAll": true 36 | }, 37 | "editor.defaultFormatter": "ms-python.black-formatter" 38 | }, 39 | "sqltools.connections": [ 40 | { 41 | "name": "Local database", 42 | "driver": "PostgreSQL", 43 | "server": "localhost", 44 | "port": 5432, 45 | "database": "postgres", 46 | "username": "admin", 47 | "password": "LocalPasswordOnly" 48 | }, 49 | { 50 | "name": "Azure database", 51 | "driver": "PostgreSQL", 52 | "server": ".postgres.database.azure.com", 53 | "port": 5432, 54 | "database": "postgres", 55 | "username": "", 56 | "askForPassword": true, 57 | "pgOptions": { 58 | "ssl": true 59 | } 60 | } 61 | ], 62 | "files.exclude": { 63 | ".ruff_cache": true, 64 | ".pytest_cache": true 65 | } 66 | } 67 | } 68 | }, 69 | // Use 'postCreateCommand' to run commands after the container is created. 70 | "postCreateCommand": "pip install -r requirements.txt", 71 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 72 | "remoteUser": "vscode" 73 | } -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | app: 5 | build: 6 | context: .. 7 | dockerfile: .devcontainer/Dockerfile 8 | args: 9 | # [Choice] Python version: 3, 3.8, 3.7, 3.6 10 | IMAGE: python:3.11 11 | 12 | volumes: 13 | - ..:/workspace:cached 14 | 15 | # Overrides default command so things don't shut down after the process ends. 16 | command: sleep infinity 17 | 18 | # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. 19 | network_mode: service:db 20 | 21 | db: 22 | image: postgres:latest 23 | restart: unless-stopped 24 | volumes: 25 | - postgres-data:/var/lib/postgresql/data 26 | environment: 27 | POSTGRES_DB: postgres 28 | POSTGRES_USER: admin 29 | POSTGRES_PASSWORD: LocalPasswordOnly 30 | 31 | # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. 32 | # (Adding the "ports" property to this file will not forward from a Codespace.) 33 | 34 | volumes: 35 | postgres-data: -------------------------------------------------------------------------------- /.env.azure: -------------------------------------------------------------------------------- 1 | DBHOST=HOSTNAME.postgres.database.azure.com 2 | DBUSER=USERNAME 3 | DBPASS=ServerPassword 4 | DBNAME=postgres 5 | -------------------------------------------------------------------------------- /.env.devcontainer: -------------------------------------------------------------------------------- 1 | DBHOST=localhost 2 | DBUSER=admin 3 | DBPASS=LocalPasswordOnly 4 | DBNAME=postgres 5 | -------------------------------------------------------------------------------- /.github/dependabot-bot.yml: -------------------------------------------------------------------------------- 1 | safe: 2 | - psycopg2 3 | - python-dotenv 4 | - SQLAlchemy 5 | - faker 6 | - pandas 7 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Python check 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test_package: 11 | name: Test ${{ matrix.os }} Python ${{ matrix.python_version }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: ["ubuntu-20.04"] 17 | python_version: ["3.9", "3.10", "3.11", "3.12"] 18 | services: 19 | postgres: 20 | image: postgres:11 21 | env: 22 | POSTGRES_PASSWORD: postgres 23 | ports: 24 | - 5432:5432 25 | # needed because the postgres container does not provide a healthcheck 26 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Setup python 30 | uses: actions/setup-python@v2 31 | with: 32 | python-version: ${{ matrix.python_version }} 33 | architecture: x64 34 | - name: Install dependencies 35 | run: | 36 | python3 -m pip install --upgrade pip 37 | python3 -m pip install -r requirements.txt 38 | - name: Run Pytest tests 39 | run: | 40 | python3 main_psycopg.py 41 | python3 main_sqlalchemy.py 42 | env: 43 | DBHOST: localhost 44 | DBUSER: postgres 45 | DBPASS: postgres 46 | DBNAME: postgres 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .azure 3 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PostgreSQL Dev Container / Codespace 2 | 3 | This is a PostgreSQL dev container for use with VS Code Remote Containers or GitHub Codespaces. 4 | The devcontainer.json uses a docker-compose.yaml to set up a local PostgreSQL server inside the container. 5 | 6 | For use with the local PostgreSQL server, copy `.env.devcontainer` into `.env`. 7 | 8 | For use with an Azure PostgreSQL server, copy `.env.azure` into `.env` and adjust the host name, user name, and password. 9 | 10 | Then run either `main_psycopg.py` or `main_sqlalchemy.py` to interact with the database. -------------------------------------------------------------------------------- /main_psycopg.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import psycopg2 4 | from dotenv import load_dotenv 5 | 6 | # Connect to the database 7 | load_dotenv(".env", override=True) 8 | DBUSER = os.environ["DBUSER"] 9 | DBPASS = os.environ["DBPASS"] 10 | DBHOST = os.environ["DBHOST"] 11 | DBNAME = os.environ["DBNAME"] 12 | # Use SSL if not connecting to localhost 13 | sslmode = "disable" 14 | if DBHOST != "localhost": 15 | sslmode = "require" 16 | conn = psycopg2.connect(database=DBNAME, user=DBUSER, password=DBPASS, host=DBHOST, sslmode=sslmode) 17 | cur = conn.cursor() 18 | cur.execute("DROP TABLE IF EXISTS restaurants") 19 | cur.execute("CREATE TABLE restaurants (id SERIAL PRIMARY KEY,name VARCHAR(255) NOT NULL)") 20 | cur.execute("INSERT INTO restaurants (id, name) VALUES ('3', 'test')") 21 | conn.commit() 22 | cur.close() 23 | -------------------------------------------------------------------------------- /main_sqlalchemy.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | from sqlalchemy import String, create_engine, select 5 | from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column 6 | 7 | 8 | # Define the models 9 | class Base(DeclarativeBase): 10 | pass 11 | 12 | 13 | class Restaurant(Base): 14 | __tablename__ = "restaurants" 15 | id: Mapped[int] = mapped_column(primary_key=True) 16 | name: Mapped[str] = mapped_column(String) 17 | address: Mapped[str] = mapped_column(String, nullable=True) 18 | 19 | 20 | # Connect to the database 21 | load_dotenv(".env", override=True) 22 | DBUSER = os.environ["DBUSER"] 23 | DBPASS = os.environ["DBPASS"] 24 | DBHOST = os.environ["DBHOST"] 25 | DBNAME = os.environ["DBNAME"] 26 | DATABASE_URI = f"postgresql://{DBUSER}:{DBPASS}@{DBHOST}/{DBNAME}" 27 | if DBHOST != "localhost": 28 | DATABASE_URI += "?sslmode=require" 29 | engine = create_engine(DATABASE_URI, echo=True) 30 | 31 | # Create tables in database 32 | Base.metadata.drop_all(engine) 33 | Base.metadata.create_all(engine) 34 | 35 | # Insert data and issue queries 36 | with Session(engine) as session: 37 | for i in range(10): 38 | session.add(Restaurant(name=f"Cheese Shop #{i}")) 39 | session.commit() 40 | 41 | query = select(Restaurant) 42 | results = session.execute(query) 43 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | line-length = 120 3 | target-version = "py311" 4 | select = ["E", "F", "I", "UP"] 5 | ignore = ["D203"] 6 | 7 | [tool.black] 8 | line-length = 120 9 | target-version = ["py311"] 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | psycopg2==2.9.9 2 | python-dotenv==1.0.1 3 | SQLAlchemy==2.0.30 4 | faker==25.8.0 5 | pandas==2.2.2 6 | -------------------------------------------------------------------------------- /restaurants.csv: -------------------------------------------------------------------------------- 1 | id,name,address 2 | 1,El Pollo Loco,123 Main St 3 | 2,Chipotle,456 Main St 4 | 3,McDonalds,789 Main St 5 | 4,In-N-Out,101 Main St 6 | --------------------------------------------------------------------------------