Skip to content

Support Python 3.10 & SQLAlchemy 2.x #17

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 2 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
SQLALCHEMY_DATABASE_URI=mysql+pymysql://myuser:mypassword@host.example.com:1234/mydatabase
SQLALCHEMY_DATABASE_PEM="-----BEGIN CERTIFICATE-----\nghdfigfjvgkjdfvfjkhcvdfjhvfghjbfdvfjshdvjghvfgjvcfjdcvckdjh\n-----END CERTIFICATE-----\n"
DATABASE_USERNAME="yourusername"
DATABASE_PASSWORD="yourpassword"
DATABASE_HOST="db.host.com"
DATABASE_PORT=12345
DATABASE_TABLE="table"
DATABASE_CERT_FILE="ca-certificate.crt"
File renamed without changes
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ venv/
ENV/
env.bak/
venv.bak/
creds/
**/*.crt

# Spyder project settings
.spyderproject
Expand All @@ -115,6 +117,7 @@ ca-certificate.crt

# Idea
.idea/
.vscode/

# logs
logs/*
Expand Down
98 changes: 59 additions & 39 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,71 +1,91 @@
SRCPATH := $(shell pwd)
PROJECTNAME := $(shell basename $(CURDIR))
ENTRYPOINT := $(PROJECTNAME).ini
PROJECT_NAME := $(shell basename $CURDIR)
VIRTUAL_ENV := $(CURDIR)/.venv
LOCAL_PYTHON := $(VIRTUAL_ENV)/bin/python3

define HELP
Manage $(PROJECTNAME). Usage:

make run - Run $(PROJECTNAME).
make deploy - Pull latest build and deploy to production.
make update - Update pip dependencies via Python Poetry.
make format - Format code with Python's `Black` library.
make lint - Check code formatting with flake8
make clean - Remove cached files and lock files.
endef
export HELP

Manage $(PROJECT_NAME). Usage:

.PHONY: run restart deploy update format lint clean help
make run - Run $(PROJECT_NAME) locally.
make install - Create local virtualenv & install dependencies.
make deploy - Set up project & run locally.
make update - Update dependencies via Poetry and output resulting `requirements.txt`.
make format - Run Python code formatter & sort dependencies.
make lint - Check code formatting with flake8.
make clean - Remove extraneous compiled files, caches, logs, etc.

requirements: .requirements.txt
env: ./.venv/bin/activate
endef
export HELP

.requirements.txt: requirements.txt
$(shell . .venv/bin/activate && pip install -r requirements.txt)

.PHONY: run install deploy update format lint clean help

all help:
@echo "$$HELP"

env: $(VIRTUAL_ENV)

$(VIRTUAL_ENV):
if [ ! -d $(VIRTUAL_ENV) ]; then \
echo "Creating Python virtual env in \`${VIRTUAL_ENV}\`"; \
python3 -m venv $(VIRTUAL_ENV); \
fi

.PHONY: dev
dev: env
$(LOCAL_PYTHON) -m main --reload

.PHONY: run
run: env
python main.py
$(LOCAL_PYTHON) -m main

.PHONY: install
install: env
$(LOCAL_PYTHON) -m pip install --upgrade pip setuptools wheel && \
$(LOCAL_PYTHON) -m pip install -r requirements.txt && \
echo Installed dependencies in \`${VIRTUAL_ENV}\`;

.PHONY: deploy
deploy:
make clean
$(shell . ./deploy.sh)
make install && \
make run

.PHONY: test
test: env
$(LOCAL_PYTHON) -m \
coverage run -m pytest -vv \
--disable-pytest-warnings && \
coverage html --title='Coverage Report' -d .reports && \
open .reports/index.html

.PHONY: update
update: env
.venv/bin/python3 -m pip install --upgrade pip setuptools wheel
poetry update
poetry export -f requirements.txt --output requirements.txt --without-hashes

$(LOCAL_PYTHON) -m pip install --upgrade pip setuptools wheel && \
poetry update && \
poetry export -f requirements.txt --output requirements.txt --without-hashes && \
echo Installed dependencies in \`${VIRTUAL_ENV}\`;

.PHONY: format
format: env
$(shell . .venv/bin/activate && isort ./)
$(shell . .venv/bin/activate && black ./)

$(LOCAL_PYTHON) -m isort --multi-line=3 . && \
$(LOCAL_PYTHON) -m black .

.PHONY: lint
lint:
flake8 . --count \
lint: env
$(LOCAL_PYTHON) -m flake8 . --count \
--select=E9,F63,F7,F82 \
--exclude .git,.github,__pycache__,.pytest_cache,.venv,logs,creds,.venv,docs,logs \
--exclude .git,.github,__pycache__,.pytest_cache,.venv,logs,creds,.venv,docs,logs,.reports \
--show-source \
--statistics


.PHONY: clean
clean:
find . -name '*.pyc' -delete
find . -name '__pycache__' -delete
find . -name 'poetry.lock' -delete
find . -name 'Pipefile.lock' -delete
find . -name 'logs/*' -delete
find . -name '.pytest_cache' -delete
find . -name 'poetry.lock' -delete && \
find . -name '.coverage' -delete && \
find . -name '.Pipfile.lock' -delete && \
find . -wholename '**/*.pyc' -delete && \
find . -type d -wholename '__pycache__' -exec rm -rf {} + && \
find . -type d -wholename './.venv' -exec rm -rf {} + && \
find . -type d -wholename '.pytest_cache' -exec rm -rf {} + && \
find . -type d -wholename '**/.pytest_cache' -exec rm -rf {} + && \
find . -type d -wholename './logs/*.log' -exec rm -rf {} + && \
find . -type d -wholename './.reports/*' -exec rm -rf {} +
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# SQLAlchemy Tutorial

![Python](https://img.shields.io/badge/Python-v^3.8-blue.svg?logo=python&longCache=true&logoColor=white&colorB=5e81ac&style=flat-square&colorA=4c566a)
![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy-v^1.4.0-blue.svg?longCache=true&logo=python&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a)
![PyMySQL](https://img.shields.io/badge/PyMySQL-v^1.0.0-red.svg?longCache=true&style=flat-square&logo=scala&logoColor=white&colorA=4c566a&colorB=bf616a)
![Python](https://img.shields.io/badge/Python-v^3.10-blue.svg?logo=python&longCache=true&logoColor=white&colorB=5e81ac&style=flat-square&colorA=4c566a)
![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy-v^2.0.20-blue.svg?longCache=true&logo=python&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a)
![PyMySQL](https://img.shields.io/badge/PyMySQL-v^1.1.0-red.svg?longCache=true&style=flat-square&logo=scala&logoColor=white&colorA=4c566a&colorB=bf616a)
![GitHub Last Commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square&colorA=4c566a&colorB=a3be8c&logo=GitHub)
[![GitHub Issues](https://img.shields.io/github/issues/hackersandslackers/sqlalchemy-tutorial.svg?style=flat-square&colorA=4c566a&logo=GitHub&colorB=ebcb8b)](https://github.com/hackersandslackers/sqlalchemy-tutorial/issues)
[![GitHub Stars](https://img.shields.io/github/stars/hackersandslackers/sqlalchemy-tutorial.svg?style=flat-square&colorA=4c566a&logo=GitHub&colorB=ebcb8b)](https://github.com/hackersandslackers/sqlalchemy-tutorial/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/hackersandslackers/sqlalchemy-tutorial.svg?style=flat-square&colorA=4c566a&logo=GitHub&colorB=ebcb8b)](https://github.com/hackersandslackers/sqlalchemy-tutorial/network)

![SQLAlchemy Tutorial](https://github.com/hackersandslackers/sqlalchemy-tutorial/blob/master/.github/sqlalchemy@2x.jpg?raw=true)
![SQLAlchemy Tutorial](https://github.com/hackersandslackers/sqlalchemy-tutorial/blob/master/.github/img/sqlalchemy@2x.jpg?raw=true)

This repository contains the source code for a four-part tutorial series on SQLAlchemy:

Expand All @@ -17,29 +17,32 @@ This repository contains the source code for a four-part tutorial series on SQLA
3. [Relationships in SQLAlchemy Data Models](https://hackersandslackers.com/sqlalchemy-data-models)
4. [Constructing Database Queries with SQLAlchemy](https://hackersandslackers.com/database-queries-sqlalchemy-orm)

# Getting Started
## Getting Started

Get set up locally in two steps:

### Environment Variables

Replace the values in **.env.example** with your values and rename this file to **.env**:


* `SQLALCHEMY_DATABASE_URI`: Connection URI of a SQL database.
* `SQLALCHEMY_DATABASE_PEM` _(Optional)_: PEM key for databases requiring an SSL connection.
* `DATABASE_USERNAME`: Username for a SQL database.
* `DATABASE_PASSWORD`: Corresponding password for the above SQL database user.
* `DATABASE_HOST`: Host of the SQL database.
* `DATABASE_PORT`: Numerical port of the SQL database.
* `DATABASE_TABLE`: Name of the SQL database table.
* `DATABASE_CERT_FILE` _(optional)_: Path to SSL certificate file for database.

*Remember never to commit secrets saved in .env files to Github.*

### Installation

Get up and running with `make deploy`:
Get up and running with `make run`:

```shell
$ git clone https://github.com/hackersandslackers/sqlalchemy-tutorial.git
$ cd sqlalchemy-tutorial
$ make deploy
```
git clone https://github.com/hackersandslackers/sqlalchemy-tutorial.git
cd sqlalchemy-tutorial
make run
```

-----

Expand Down
12 changes: 9 additions & 3 deletions config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Database config."""
from os import environ, path
from os import getenv, path

from dotenv import load_dotenv

Expand All @@ -8,8 +8,14 @@
load_dotenv(path.join(basedir, ".env"))

# Database connection variables
SQLALCHEMY_DATABASE_URI = environ.get("SQLALCHEMY_DATABASE_URI")
SQLALCHEMY_DATABASE_PEM = environ.get("SQLALCHEMY_DATABASE_PEM")
DATABASE_USERNAME = getenv("DATABASE_USERNAME")
DATABASE_PASSWORD = getenv("DATABASE_PASSWORD")
DATABASE_HOST = getenv("DATABASE_HOST")
DATABASE_PORT = getenv("DATABASE_PORT")
DATABASE_TABLE = getenv("DATABASE_TABLE")
DATABASE_CERT_FILE = getenv("DATABASE_CERT_FILE")

SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{DATABASE_USERNAME}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_TABLE}?ssl_ca={DATABASE_CERT_FILE}"

# Reset data after each run
CLEANUP_DATA = False
13 changes: 12 additions & 1 deletion database/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
from .connect import engine, session
"""Create SQLAlchemy engine and session objects."""
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from config import SQLALCHEMY_DATABASE_URI

# Create database engine
engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=False)

# Create database session
Session = sessionmaker(bind=engine)
session = Session()
14 changes: 0 additions & 14 deletions database/connect.py

This file was deleted.

12 changes: 0 additions & 12 deletions deploy.sh

This file was deleted.

35 changes: 10 additions & 25 deletions logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,19 @@ def formatter(log: dict) -> str:
"""
Format log colors based on level.

:param log: Logged event stored as map containing contextual metadata.
:type log: dict
:param dict log: Logged event stored as map containing contextual metadata.

:returns: str
"""
if log["level"].name == "INFO":
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #b3cfe7>{level}</fg #b3cfe7>: <light-white>{message}</light-white>\n"
if log["level"].name == "WARNING":
return (
"<white>{time:MM-DD-YYYY HH:mm:ss}</white> | "
"<light-yellow>{level}</light-yellow>: "
"<light-white>{message}</light-white> \n"
)
elif log["level"].name == "ERROR":
return (
"<white>{time:MM-DD-YYYY HH:mm:ss}</white> | "
"<light-red>{level}</light-red>: "
"<light-white>{message}</light-white> \n"
)
elif log["level"].name == "SUCCESS":
return (
"<white>{time:MM-DD-YYYY HH:mm:ss}</white> | "
"<light-green>{level}</light-green>: "
"<light-white>{message}</light-white> \n"
)
else:
return (
"<white>{time:MM-DD-YYYY HH:mm:ss}</white> | "
"<fg #67c9c4>{level}</fg #67c9c4>: "
"<light-white>{message}</light-white> \n"
)
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #b09057>{level}</fg #b09057>: <light-white>{message}</light-white>\n"
if log["level"].name == "SUCCESS":
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #6dac77>{level}</fg #6dac77>: <light-white>{message}</light-white>\n"
if log["level"].name == "ERROR":
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #a35252>{level}</fg #a35252>: <light-white>{message}</light-white>\n"
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #b3cfe7>{level}</fg #b3cfe7>: <light-white>{message}</light-white>\n"


def create_logger() -> custom_logger:
Expand Down
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[mypy]
python_version = 3.8
python_version = 3.10
warn_return_any = True
warn_unused_configs = True
Loading